@aigne/doc-smith 0.9.7-beta.3 → 0.9.8-alpha.0
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/CHANGELOG.md +22 -0
- package/agents/create/analyze-diagram-type-llm.yaml +160 -0
- package/agents/create/analyze-diagram-type.mjs +297 -0
- package/agents/create/check-need-generate-structure.mjs +1 -34
- package/agents/create/generate-diagram-image.yaml +60 -0
- package/agents/create/index.yaml +9 -5
- package/agents/create/replace-d2-with-image.mjs +625 -0
- package/agents/create/user-review-document-structure.mjs +8 -7
- package/agents/create/utils/init-current-content.mjs +5 -9
- package/agents/evaluate/document.yaml +6 -0
- package/agents/evaluate/index.yaml +1 -0
- package/agents/init/index.mjs +36 -388
- package/agents/localize/index.yaml +4 -4
- package/agents/media/batch-generate-media-description.yaml +2 -0
- package/agents/media/generate-media-description.yaml +3 -0
- package/agents/media/load-media-description.mjs +44 -15
- package/agents/publish/index.yaml +1 -0
- package/agents/publish/publish-docs.mjs +1 -4
- package/agents/update/check-diagram-flag.mjs +116 -0
- package/agents/update/check-document.mjs +0 -1
- package/agents/update/check-generate-diagram.mjs +48 -30
- package/agents/update/check-sync-image-flag.mjs +55 -0
- package/agents/update/check-update-is-single.mjs +11 -0
- package/agents/update/generate-diagram.yaml +43 -9
- package/agents/update/generate-document.yaml +9 -0
- package/agents/update/handle-document-update.yaml +10 -8
- package/agents/update/index.yaml +25 -7
- package/agents/update/sync-images-and-exit.mjs +148 -0
- package/agents/update/update-single/update-single-document-detail.mjs +131 -17
- package/agents/utils/analyze-feedback-intent.mjs +136 -0
- package/agents/utils/choose-docs.mjs +185 -40
- package/agents/utils/generate-document-or-skip.mjs +41 -0
- package/agents/utils/handle-diagram-operations.mjs +263 -0
- package/agents/utils/load-all-document-content.mjs +30 -0
- package/agents/utils/load-sources.mjs +2 -2
- package/agents/utils/post-generate.mjs +14 -3
- package/agents/utils/read-current-document-content.mjs +46 -0
- package/agents/utils/save-doc-translation.mjs +34 -0
- package/agents/utils/save-doc.mjs +42 -0
- package/agents/utils/save-sidebar.mjs +19 -6
- package/agents/utils/skip-if-content-exists.mjs +27 -0
- package/aigne.yaml +15 -3
- package/assets/report-template/report.html +17 -17
- package/docs-mcp/read-doc-content.mjs +30 -1
- package/package.json +8 -7
- package/prompts/detail/diagram/generate-image-system.md +135 -0
- package/prompts/detail/diagram/generate-image-user.md +32 -0
- package/prompts/detail/generate/user-prompt.md +27 -13
- package/prompts/evaluate/document.md +23 -10
- package/prompts/media/media-description/system-prompt.md +10 -2
- package/prompts/media/media-description/user-prompt.md +9 -0
- package/utils/check-document-has-diagram.mjs +95 -0
- package/utils/constants/index.mjs +46 -0
- package/utils/d2-utils.mjs +119 -178
- package/utils/delete-diagram-images.mjs +99 -0
- package/utils/docs-finder-utils.mjs +133 -25
- package/utils/image-compress.mjs +75 -0
- package/utils/kroki-utils.mjs +2 -3
- package/utils/load-config.mjs +29 -0
- package/utils/sync-diagram-to-translations.mjs +262 -0
- package/utils/utils.mjs +24 -0
- package/agents/create/check-diagram.mjs +0 -40
- package/agents/create/draw-diagram.yaml +0 -27
- package/agents/create/merge-diagram.yaml +0 -39
- package/agents/create/wrap-diagram-code.mjs +0 -35
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { AIAgent } from "@aigne/core";
|
|
2
|
+
import { pick } from "@aigne/core/utils/type-utils.js";
|
|
3
|
+
import z from "zod";
|
|
4
|
+
import {
|
|
5
|
+
DIAGRAM_PLACEHOLDER,
|
|
6
|
+
replaceD2WithPlaceholder,
|
|
7
|
+
replaceDiagramsWithPlaceholder,
|
|
8
|
+
} from "../../utils/d2-utils.mjs";
|
|
9
|
+
import { readFileContent } from "../../utils/docs-finder-utils.mjs";
|
|
10
|
+
import { getFileName, userContextAt } from "../../utils/utils.mjs";
|
|
11
|
+
import { debug } from "../../utils/debug.mjs";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read current document content from file system
|
|
15
|
+
* Reuses the same logic as initCurrentContent but returns the content
|
|
16
|
+
* First checks userContext, then reads from file if not in context
|
|
17
|
+
*/
|
|
18
|
+
async function readCurrentContent(input, options) {
|
|
19
|
+
const { path, docsDir, locale = "en" } = input;
|
|
20
|
+
|
|
21
|
+
if (!path || !docsDir) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// First check if content is already in userContext
|
|
26
|
+
const contentContext = userContextAt(options, `currentContents.${path}`);
|
|
27
|
+
const existingContent = contentContext.get();
|
|
28
|
+
if (existingContent) {
|
|
29
|
+
return existingContent;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If not in context, read from file (same logic as initCurrentContent)
|
|
33
|
+
try {
|
|
34
|
+
const fileName = getFileName(path, locale);
|
|
35
|
+
const content = await readFileContent(docsDir, fileName);
|
|
36
|
+
|
|
37
|
+
if (!content) {
|
|
38
|
+
console.warn(`⚠️ Could not read content from ${fileName}`);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Set in userContext for future use
|
|
43
|
+
contentContext.set(content);
|
|
44
|
+
|
|
45
|
+
return content;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn(`Failed to read current content for ${path}: ${error.message}`);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Save document content
|
|
54
|
+
*/
|
|
55
|
+
async function saveDoc(input, options, { content, intentType }) {
|
|
56
|
+
const saveAgent = options.context?.agents?.["saveDoc"];
|
|
57
|
+
if (!saveAgent) {
|
|
58
|
+
console.warn("saveDoc agent not found");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
await options.context.invoke(saveAgent, {
|
|
62
|
+
...pick(input, ["path", "docsDir", "labels", "locale"]),
|
|
63
|
+
content,
|
|
64
|
+
intentType, // Pass intentType so saveDoc can handle translation sync
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Handle addDiagram intent
|
|
70
|
+
*/
|
|
71
|
+
async function addDiagram(input, options) {
|
|
72
|
+
// readCurrentContent will check userContext first, then read from file if needed
|
|
73
|
+
const currentContent = await readCurrentContent(input, options);
|
|
74
|
+
if (!currentContent) {
|
|
75
|
+
throw new Error(`Could not read current content for ${input.path}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const contentContext = userContextAt(options, `currentContents.${input.path}`);
|
|
79
|
+
|
|
80
|
+
const generateDiagramAgent = options.context.agents["checkGenerateDiagram"];
|
|
81
|
+
if (!generateDiagramAgent) {
|
|
82
|
+
throw new Error("checkGenerateDiagram agent not found");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const generateDiagramResult = await options.context.invoke(generateDiagramAgent, {
|
|
86
|
+
...pick(input, ["locale", "diagramming", "feedback", "path", "docsDir"]),
|
|
87
|
+
documentContent: currentContent,
|
|
88
|
+
originalContent: currentContent,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const content = generateDiagramResult.content;
|
|
92
|
+
contentContext.set(content);
|
|
93
|
+
// Pass intentType to saveDoc so it can handle translation sync automatically
|
|
94
|
+
await saveDoc(input, options, { content, intentType: "addDiagram" });
|
|
95
|
+
return { content };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Handle updateDiagram intent
|
|
100
|
+
*/
|
|
101
|
+
async function updateDiagram(input, options) {
|
|
102
|
+
// readCurrentContent will check userContext first, then read from file if needed
|
|
103
|
+
const currentContent = await readCurrentContent(input, options);
|
|
104
|
+
if (!currentContent) {
|
|
105
|
+
throw new Error(`Could not read current content for ${input.path}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const contentContext = userContextAt(options, `currentContents.${input.path}`);
|
|
109
|
+
|
|
110
|
+
let [content] = replaceD2WithPlaceholder({
|
|
111
|
+
content: currentContent,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const generateAgent = options.context?.agents?.["generateDiagram"];
|
|
115
|
+
if (!generateAgent) {
|
|
116
|
+
throw new Error("generateDiagram agent not found");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = await options.context.invoke(generateAgent, {
|
|
120
|
+
documentContent: content,
|
|
121
|
+
locale: input.locale,
|
|
122
|
+
diagramming: input.diagramming || {},
|
|
123
|
+
feedback: input.feedback,
|
|
124
|
+
originalContent: currentContent, // Pass original content to find existing diagrams
|
|
125
|
+
path: input.path,
|
|
126
|
+
docsDir: input.docsDir,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// generateDiagram now returns { content } with image already inserted
|
|
130
|
+
// The image replaces DIAGRAM_PLACEHOLDER or D2 code blocks
|
|
131
|
+
if (result?.content) {
|
|
132
|
+
content = result.content;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
contentContext.set(content);
|
|
136
|
+
// Pass intentType to saveDoc so it can handle translation sync automatically
|
|
137
|
+
await saveDoc(input, options, { content, intentType: "updateDiagram" });
|
|
138
|
+
return { content };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Handle deleteDiagram intent
|
|
143
|
+
*/
|
|
144
|
+
async function deleteDiagram(input, options) {
|
|
145
|
+
// readCurrentContent will check userContext first, then read from file if needed
|
|
146
|
+
const currentContent = await readCurrentContent(input, options);
|
|
147
|
+
if (!currentContent) {
|
|
148
|
+
throw new Error(`Could not read current content for ${input.path}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const contentContext = userContextAt(options, `currentContents.${input.path}`);
|
|
152
|
+
|
|
153
|
+
// Extract diagram index from feedback if provided
|
|
154
|
+
// This allows deleting a specific diagram when multiple diagrams exist
|
|
155
|
+
let diagramIndex = input.diagramIndex;
|
|
156
|
+
if (diagramIndex === undefined && input.feedback) {
|
|
157
|
+
// Import extractDiagramIndexFromFeedback from replace-d2-with-image.mjs
|
|
158
|
+
const { extractDiagramIndexFromFeedback } = await import("../create/replace-d2-with-image.mjs");
|
|
159
|
+
const extractedIndex = extractDiagramIndexFromFeedback(input.feedback);
|
|
160
|
+
if (extractedIndex !== null) {
|
|
161
|
+
diagramIndex = extractedIndex;
|
|
162
|
+
debug(`Extracted diagram index ${diagramIndex} from feedback: "${input.feedback}"`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Replace all diagrams (D2 code blocks, generated images, Mermaid) with placeholder
|
|
167
|
+
// If diagramIndex is provided, only replace that specific diagram
|
|
168
|
+
// This ensures LLM can identify and remove the diagram regardless of its type
|
|
169
|
+
const documentContent = replaceDiagramsWithPlaceholder({
|
|
170
|
+
content: currentContent,
|
|
171
|
+
diagramIndex,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const instructions = `<role>
|
|
175
|
+
Your task is to remove ${DIAGRAM_PLACEHOLDER} and adjust the document context (based on the user's feedback) to make it easier to understand.
|
|
176
|
+
</role>
|
|
177
|
+
|
|
178
|
+
<document_content>
|
|
179
|
+
{{documentContent}}
|
|
180
|
+
</document_content>
|
|
181
|
+
|
|
182
|
+
<user_feedback>
|
|
183
|
+
{{feedback}}
|
|
184
|
+
</user_feedback>
|
|
185
|
+
|
|
186
|
+
<output_constraints>
|
|
187
|
+
- Do not provide any explanations; include only the document content itself
|
|
188
|
+
</output_constraints>`;
|
|
189
|
+
|
|
190
|
+
const deleteAgent = AIAgent.from({
|
|
191
|
+
name: "deleteDiagram",
|
|
192
|
+
description: "Remove a diagram from the document content",
|
|
193
|
+
task_render_mode: "hide",
|
|
194
|
+
instructions,
|
|
195
|
+
inputSchema: z.object({
|
|
196
|
+
documentContent: z.string().describe("Source content of the document"),
|
|
197
|
+
feedback: z.string().describe("User feedback for content modifications"),
|
|
198
|
+
}),
|
|
199
|
+
outputKey: "message",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const { message: content } = await options.context.invoke(deleteAgent, {
|
|
203
|
+
documentContent,
|
|
204
|
+
feedback: input.feedback,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Delete associated diagram image files
|
|
208
|
+
if (input.docsDir) {
|
|
209
|
+
try {
|
|
210
|
+
const { deleteDiagramImages } = await import("../../utils/delete-diagram-images.mjs");
|
|
211
|
+
const { deleted } = await deleteDiagramImages(currentContent, input.path, input.docsDir);
|
|
212
|
+
if (deleted > 0) {
|
|
213
|
+
debug(`Deleted ${deleted} diagram image file(s) for ${input.path}`);
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// Don't fail the operation if image deletion fails
|
|
217
|
+
console.warn(`Failed to delete diagram images: ${error.message}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
contentContext.set(content);
|
|
222
|
+
// Pass intentType to saveDoc so it can handle translation sync automatically
|
|
223
|
+
await saveDoc(input, options, { content, intentType: "deleteDiagram" });
|
|
224
|
+
|
|
225
|
+
return { content };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Handle diagram operations based on intent type
|
|
230
|
+
* Supports: addDiagram, updateDiagram, deleteDiagram
|
|
231
|
+
*/
|
|
232
|
+
export default async function handleDiagramOperations(
|
|
233
|
+
{ intentType, path, docsDir, locale, feedback, diagramming, labels },
|
|
234
|
+
options,
|
|
235
|
+
) {
|
|
236
|
+
if (!intentType || !["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType)) {
|
|
237
|
+
throw new Error(`Invalid intent type for diagram operations: ${intentType}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const input = {
|
|
241
|
+
path,
|
|
242
|
+
docsDir,
|
|
243
|
+
locale,
|
|
244
|
+
feedback,
|
|
245
|
+
diagramming,
|
|
246
|
+
labels,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const fnMap = {
|
|
250
|
+
addDiagram,
|
|
251
|
+
updateDiagram,
|
|
252
|
+
deleteDiagram,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
if (fnMap[intentType]) {
|
|
256
|
+
const result = await fnMap[intentType](input, options);
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
throw new Error(`Unsupported intent type: ${intentType}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
handleDiagramOperations.task_render_mode = "hide";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import pMap from "p-map";
|
|
2
|
+
import { findItemByPath } from "../../utils/docs-finder-utils.mjs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Loads all document content from the file system based on the provided document structure.
|
|
6
|
+
*
|
|
7
|
+
* @async
|
|
8
|
+
* @function loadAllDocumentContent
|
|
9
|
+
* @param {Object} params - The parameters object
|
|
10
|
+
* @param {string} params.docsDir - The root directory path where documents are located
|
|
11
|
+
* @param {Array<Object>} params.documentStructure - The document structure array containing items with path information
|
|
12
|
+
* @param {string} params.documentStructure[].path - The file path of each document item
|
|
13
|
+
* @returns {Promise<Array>} returns.allDocumentContentList - An array of document content items loaded from the file system
|
|
14
|
+
* @example
|
|
15
|
+
* const result = await loadAllDocumentContent({
|
|
16
|
+
* docsDir: './docs',
|
|
17
|
+
* documentStructure: [{ path: 'readme.md' }, { path: 'guide.md' }]
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
export default async function loadAllDocumentContent({ docsDir, documentStructure = [] }) {
|
|
21
|
+
const allDocumentContentList = await pMap(documentStructure, async (item) => {
|
|
22
|
+
const itemResult = await findItemByPath(documentStructure, item.path, null, docsDir);
|
|
23
|
+
return itemResult;
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
allDocumentContentList,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
loadAllDocumentContent.taskRenderMode = "hide";
|
|
@@ -157,8 +157,8 @@ export default async function loadSources(
|
|
|
157
157
|
mediaItem.width = dimensions.width;
|
|
158
158
|
mediaItem.height = dimensions.height;
|
|
159
159
|
|
|
160
|
-
// Filter out images with width less than minImageWidth
|
|
161
|
-
if (dimensions.width < minImageWidth) {
|
|
160
|
+
// Filter out images with width less than minImageWidth, but not SVG images
|
|
161
|
+
if (dimensions.width < minImageWidth && mediaItem.mimeType !== "image/svg+xml") {
|
|
162
162
|
filteredImageCount++;
|
|
163
163
|
console.log(
|
|
164
164
|
`Ignored image: ${fileName} (${dimensions.width}x${dimensions.height}px < ${minImageWidth}px minimum)`,
|
|
@@ -16,6 +16,8 @@ export default async function postGenerate({
|
|
|
16
16
|
translateLanguages = [],
|
|
17
17
|
locale,
|
|
18
18
|
projectInfoMessage,
|
|
19
|
+
isChat,
|
|
20
|
+
feedback,
|
|
19
21
|
}) {
|
|
20
22
|
const _results = [];
|
|
21
23
|
// Save current git HEAD to config.yaml for change detection
|
|
@@ -33,12 +35,21 @@ export default async function postGenerate({
|
|
|
33
35
|
console.error("Failed to cleanup invalid .md files:", err.message);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
let message = `
|
|
37
39
|
✅ Documentation generated successfully! (\`${documentStructure.length}\` documents → \`${docsDir}\`)
|
|
38
|
-
${projectInfoMessage || ""}
|
|
40
|
+
${projectInfoMessage || ""} `;
|
|
41
|
+
|
|
42
|
+
if (!isChat) {
|
|
43
|
+
message += `
|
|
39
44
|
🚀 Next: Make your documentation live and generate a shareable link, run: \`aigne doc publish\`
|
|
40
45
|
💡 Optional: Update specific document (\`aigne doc update\`) or refine documentation structure (\`aigne doc create\`)
|
|
41
|
-
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isChat && feedback) {
|
|
50
|
+
message = `User feedback: ${feedback}
|
|
51
|
+
The documentation structure and relevant documents have been updated according to the user's feedback. All feedback has been fully processed.`;
|
|
52
|
+
}
|
|
42
53
|
|
|
43
54
|
// Shutdown mermaid worker pool to ensure clean exit
|
|
44
55
|
try {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileContent } from "../../utils/docs-finder-utils.mjs";
|
|
2
|
+
import { getFileName } from "../../utils/utils.mjs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Read current document content from file system
|
|
6
|
+
* Used when skipping document generation (e.g., for diagram-only updates)
|
|
7
|
+
* Only reads content if intentType is diagram-related, otherwise returns input unchanged
|
|
8
|
+
*/
|
|
9
|
+
export default async function readCurrentDocumentContent(input) {
|
|
10
|
+
const { path, docsDir, locale = "en", intentType } = input;
|
|
11
|
+
|
|
12
|
+
// Only read content if intentType is diagram-related
|
|
13
|
+
// Otherwise, return input unchanged to allow document generation to proceed
|
|
14
|
+
if (!intentType || !["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType)) {
|
|
15
|
+
return input;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!path || !docsDir) {
|
|
19
|
+
return input;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Read document content using the same utility as other parts of the system
|
|
24
|
+
const fileName = getFileName(path, locale);
|
|
25
|
+
const content = await readFileContent(docsDir, fileName);
|
|
26
|
+
|
|
27
|
+
if (!content) {
|
|
28
|
+
console.warn(`⚠️ Could not read content from ${fileName}`);
|
|
29
|
+
return input;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Return content as both content, documentContent, and originalContent
|
|
33
|
+
// This matches the structure expected by downstream agents
|
|
34
|
+
return {
|
|
35
|
+
...input,
|
|
36
|
+
content,
|
|
37
|
+
documentContent: content,
|
|
38
|
+
originalContent: content,
|
|
39
|
+
};
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.warn(`Failed to read current content for ${path}: ${error.message}`);
|
|
42
|
+
return input;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
readCurrentDocumentContent.task_render_mode = "hide";
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { saveDocTranslation as _saveDocTranslation } from "../../utils/utils.mjs";
|
|
2
|
+
import { readFileContent } from "../../utils/docs-finder-utils.mjs";
|
|
3
|
+
import { getFileName } from "../../utils/utils.mjs";
|
|
4
|
+
import { debug } from "../../utils/debug.mjs";
|
|
5
|
+
import { syncDiagramToTranslations } from "../../utils/sync-diagram-to-translations.mjs";
|
|
2
6
|
|
|
3
7
|
export default async function saveDocTranslation({
|
|
4
8
|
path,
|
|
@@ -7,6 +11,7 @@ export default async function saveDocTranslation({
|
|
|
7
11
|
language,
|
|
8
12
|
labels,
|
|
9
13
|
isShowMessage = false,
|
|
14
|
+
locale,
|
|
10
15
|
}) {
|
|
11
16
|
await _saveDocTranslation({
|
|
12
17
|
path,
|
|
@@ -16,6 +21,35 @@ export default async function saveDocTranslation({
|
|
|
16
21
|
labels,
|
|
17
22
|
});
|
|
18
23
|
|
|
24
|
+
// Sync diagram images from main document to translations
|
|
25
|
+
// This ensures all images (including diagrams) in the main document are synced to translation files
|
|
26
|
+
if (path && docsDir && locale) {
|
|
27
|
+
try {
|
|
28
|
+
// Read main document content (it should already be saved)
|
|
29
|
+
const mainFileName = getFileName(path, locale);
|
|
30
|
+
const mainContent = await readFileContent(docsDir, mainFileName);
|
|
31
|
+
|
|
32
|
+
if (mainContent) {
|
|
33
|
+
const syncResult = await syncDiagramToTranslations(
|
|
34
|
+
mainContent,
|
|
35
|
+
path,
|
|
36
|
+
docsDir,
|
|
37
|
+
locale,
|
|
38
|
+
"sync",
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (syncResult.updated > 0) {
|
|
42
|
+
debug(
|
|
43
|
+
`✅ Synced diagram images to ${syncResult.updated} translation file(s) after translation`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Don't fail the translation if sync fails
|
|
49
|
+
debug(`⚠️ Failed to sync diagram images after translation: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
19
53
|
if (isShowMessage) {
|
|
20
54
|
const message = `✅ Translation completed successfully.`;
|
|
21
55
|
return { message };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { shutdownMermaidWorkerPool } from "../../utils/mermaid-worker-pool.mjs";
|
|
2
2
|
import { saveDoc as _saveDoc } from "../../utils/utils.mjs";
|
|
3
|
+
import { debug } from "../../utils/debug.mjs";
|
|
3
4
|
|
|
4
5
|
export default async function saveDoc({
|
|
5
6
|
path,
|
|
@@ -9,6 +10,8 @@ export default async function saveDoc({
|
|
|
9
10
|
locale,
|
|
10
11
|
feedback,
|
|
11
12
|
isShowMessage = false,
|
|
13
|
+
intentType,
|
|
14
|
+
originalContent,
|
|
12
15
|
...rest
|
|
13
16
|
}) {
|
|
14
17
|
await _saveDoc({
|
|
@@ -19,6 +22,43 @@ export default async function saveDoc({
|
|
|
19
22
|
locale,
|
|
20
23
|
});
|
|
21
24
|
|
|
25
|
+
// Sync diagram changes to translation documents if needed
|
|
26
|
+
// Only sync for diagram-related operations (addDiagram, updateDiagram, deleteDiagram)
|
|
27
|
+
if (
|
|
28
|
+
docsDir &&
|
|
29
|
+
path &&
|
|
30
|
+
intentType &&
|
|
31
|
+
["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType)
|
|
32
|
+
) {
|
|
33
|
+
try {
|
|
34
|
+
const { syncDiagramToTranslations } = await import(
|
|
35
|
+
"../../utils/sync-diagram-to-translations.mjs"
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Determine operation type for sync
|
|
39
|
+
// deleteDiagram -> "delete" (process even if 0 diagrams)
|
|
40
|
+
// addDiagram/updateDiagram -> "update" (skip if 0 diagrams)
|
|
41
|
+
const operationType = intentType === "deleteDiagram" ? "delete" : "update";
|
|
42
|
+
|
|
43
|
+
const syncResult = await syncDiagramToTranslations(
|
|
44
|
+
content,
|
|
45
|
+
path,
|
|
46
|
+
docsDir,
|
|
47
|
+
locale || "en",
|
|
48
|
+
operationType,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (syncResult.updated > 0) {
|
|
52
|
+
debug(
|
|
53
|
+
`✅ Synced diagram changes to ${syncResult.updated} translation file(s) for ${intentType}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Don't fail the operation if sync fails
|
|
58
|
+
debug(`⚠️ Failed to sync diagram to translations: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
22
62
|
if (isShowMessage) {
|
|
23
63
|
// Shutdown mermaid worker pool to ensure clean exit
|
|
24
64
|
try {
|
|
@@ -39,6 +79,8 @@ export default async function saveDoc({
|
|
|
39
79
|
locale,
|
|
40
80
|
feedback,
|
|
41
81
|
isShowMessage,
|
|
82
|
+
intentType,
|
|
83
|
+
originalContent,
|
|
42
84
|
...rest,
|
|
43
85
|
};
|
|
44
86
|
}
|
|
@@ -2,10 +2,14 @@ import { join } from "node:path";
|
|
|
2
2
|
import fs from "fs-extra";
|
|
3
3
|
import { buildDocumentTree } from "../../utils/docs-finder-utils.mjs";
|
|
4
4
|
|
|
5
|
-
export default async function saveSidebar({
|
|
5
|
+
export default async function saveSidebar({
|
|
6
|
+
documentStructure,
|
|
7
|
+
originalDocumentStructure,
|
|
8
|
+
docsDir,
|
|
9
|
+
}) {
|
|
6
10
|
// Generate _sidebar.md
|
|
7
11
|
try {
|
|
8
|
-
const sidebar = generateSidebar(documentStructure);
|
|
12
|
+
const sidebar = generateSidebar(documentStructure || originalDocumentStructure);
|
|
9
13
|
const sidebarPath = join(docsDir, "_sidebar.md");
|
|
10
14
|
|
|
11
15
|
await fs.ensureDir(docsDir);
|
|
@@ -16,14 +20,23 @@ export default async function saveSidebar({ documentStructure, docsDir }) {
|
|
|
16
20
|
return {};
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
// Recursively generate sidebar text
|
|
23
|
+
// Recursively generate sidebar text
|
|
20
24
|
function walk(nodes, indent = "") {
|
|
21
25
|
let out = "";
|
|
22
26
|
for (const node of nodes) {
|
|
23
|
-
const relPath = node.path.replace(/^\//, "");
|
|
24
|
-
const flatFile = `${relPath.split("/").join("-")}.md`;
|
|
25
27
|
const realIndent = node.parentId === null ? "" : indent;
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
// If path already ends with .md, use it directly
|
|
30
|
+
let linkPath;
|
|
31
|
+
if (node.path.endsWith(".md")) {
|
|
32
|
+
linkPath = node.path.startsWith("/") ? node.path : `/${node.path}`;
|
|
33
|
+
} else {
|
|
34
|
+
// Otherwise, convert to flattened file name
|
|
35
|
+
const relPath = node.path.replace(/^\//, "");
|
|
36
|
+
linkPath = `/${relPath.split("/").join("-")}.md`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
out += `${realIndent}* [${node.title}](${linkPath})\n`;
|
|
27
40
|
|
|
28
41
|
if (node.children && node.children.length > 0) {
|
|
29
42
|
out += walk(node.children, `${indent} `);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determine whether to skip document generation when the document already exists.
|
|
3
|
+
* If intentType is diagram-related *and* content is already present, mark that
|
|
4
|
+
* generation should be skipped so downstream agents can short-circuit.
|
|
5
|
+
*/
|
|
6
|
+
export default async function skipIfContentExists(input) {
|
|
7
|
+
const { intentType, content } = input;
|
|
8
|
+
|
|
9
|
+
const isDiagramIntent =
|
|
10
|
+
intentType && ["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType);
|
|
11
|
+
const shouldSkipGeneration = Boolean(isDiagramIntent && content);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
...input,
|
|
15
|
+
skipGenerateDocument: shouldSkipGeneration,
|
|
16
|
+
// Ensure downstream steps have content available when skipping
|
|
17
|
+
...(shouldSkipGeneration
|
|
18
|
+
? {
|
|
19
|
+
content,
|
|
20
|
+
documentContent: content,
|
|
21
|
+
originalContent: content,
|
|
22
|
+
}
|
|
23
|
+
: {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
skipIfContentExists.task_render_mode = "hide";
|
package/aigne.yaml
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env aigne
|
|
2
2
|
|
|
3
3
|
model:
|
|
4
|
-
|
|
5
|
-
model: gemini-2.5-pro
|
|
4
|
+
model: gemini-3-pro-preview
|
|
5
|
+
# model: gemini-2.5-pro
|
|
6
6
|
temperature: 0.8
|
|
7
7
|
# https://github.com/AIGNE-io/aigne-framework/blob/main/models/gemini/src/gemini-chat-model.ts#L115
|
|
8
8
|
reasoning_effort:
|
|
9
9
|
$get: reasoningEffort
|
|
10
10
|
agents:
|
|
11
|
+
# - ./_exp_/index.yaml
|
|
12
|
+
# - ./_exp_/generateStructureOrchestrator.yaml
|
|
13
|
+
|
|
14
|
+
- ./agentic-agents/create/index.yaml
|
|
15
|
+
|
|
11
16
|
# Initialization
|
|
12
17
|
- ./agents/init/index.mjs
|
|
13
18
|
|
|
@@ -19,6 +24,7 @@ agents:
|
|
|
19
24
|
- ./agents/create/check-document-structure.yaml
|
|
20
25
|
- ./agents/create/user-review-document-structure.mjs
|
|
21
26
|
- ./agents/create/index.yaml
|
|
27
|
+
- ./agentic-agents/structure/index.yaml
|
|
22
28
|
|
|
23
29
|
# Documentation Structure Tools
|
|
24
30
|
- ./agents/create/document-structure-tools/add-document.mjs
|
|
@@ -105,10 +111,15 @@ agents:
|
|
|
105
111
|
- ./agents/evaluate/code-snippet.mjs
|
|
106
112
|
|
|
107
113
|
# Diagram
|
|
108
|
-
- ./agents/create/
|
|
114
|
+
- ./agents/create/analyze-diagram-type.mjs
|
|
115
|
+
- ./agents/create/analyze-diagram-type-llm.yaml
|
|
116
|
+
- ./agents/create/generate-diagram-image.yaml
|
|
117
|
+
- ./agents/create/replace-d2-with-image.mjs
|
|
109
118
|
- ./agents/update/generate-diagram.yaml
|
|
110
119
|
- ./agents/update/check-generate-diagram.mjs
|
|
111
120
|
- ./agents/update/pre-check-generate-diagram.yaml
|
|
121
|
+
- ./agents/update/check-sync-image-flag.mjs
|
|
122
|
+
- ./agents/update/sync-images-and-exit.mjs
|
|
112
123
|
cli:
|
|
113
124
|
chat: ./agents/chat/index.mjs
|
|
114
125
|
agents:
|
|
@@ -133,6 +144,7 @@ cli:
|
|
|
133
144
|
alias: ["remove", "rm"]
|
|
134
145
|
url: ./agents/create/user-remove-document/index.yaml
|
|
135
146
|
- ./agents/clear/index.yaml
|
|
147
|
+
- ./agentic-agents/create/index.yaml
|
|
136
148
|
mcp_server:
|
|
137
149
|
agents:
|
|
138
150
|
- ./docs-mcp/get-docs-structure.mjs
|