@aigne/doc-smith 0.9.7 → 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 +20 -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
|
@@ -4,18 +4,72 @@ import z from "zod";
|
|
|
4
4
|
import {
|
|
5
5
|
DIAGRAM_PLACEHOLDER,
|
|
6
6
|
replaceD2WithPlaceholder,
|
|
7
|
-
|
|
7
|
+
replaceDiagramsWithPlaceholder,
|
|
8
8
|
} from "../../../utils/d2-utils.mjs";
|
|
9
9
|
import { userContextAt } from "../../../utils/utils.mjs";
|
|
10
10
|
|
|
11
11
|
async function getIntentType(input, options) {
|
|
12
12
|
const instructions = `<role>
|
|
13
13
|
You are a feedback intent analyzer. Your task is to determine which type of content modifications are needed based on the user's feedback.
|
|
14
|
+
|
|
15
|
+
You must analyze the user's feedback and classify it into one of the following intent types:
|
|
16
|
+
- addDiagram: User wants to add a new diagram/image/chart
|
|
17
|
+
- deleteDiagram: User wants to remove/delete a diagram/image/chart
|
|
18
|
+
- updateDiagram: User wants to modify/update an existing diagram/image/chart
|
|
19
|
+
- updateDocument: User wants to update document content (text, sections, etc.) without diagram operations
|
|
14
20
|
</role>
|
|
15
21
|
|
|
22
|
+
<intent_classification_rules>
|
|
23
|
+
**deleteDiagram** - Use this when user explicitly wants to remove or delete a diagram/image/chart:
|
|
24
|
+
- Keywords: remove, delete, 删除, 移除, 去掉, 清除
|
|
25
|
+
- Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图, 架构图
|
|
26
|
+
- Examples:
|
|
27
|
+
- "Remove the diagram"
|
|
28
|
+
- "Delete the image"
|
|
29
|
+
- "删除这张图片"
|
|
30
|
+
- "Remove the second diagram"
|
|
31
|
+
- "去掉架构图"
|
|
32
|
+
- "Remove image from page 3"
|
|
33
|
+
- "Delete the chart showing the flow"
|
|
34
|
+
|
|
35
|
+
**addDiagram** - Use this when user wants to add a new diagram:
|
|
36
|
+
- Keywords: add, create, insert, 添加, 创建, 插入
|
|
37
|
+
- Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图
|
|
38
|
+
- Examples:
|
|
39
|
+
- "Add a diagram showing the architecture"
|
|
40
|
+
- "Create a flow chart"
|
|
41
|
+
- "添加一个架构图"
|
|
42
|
+
|
|
43
|
+
**updateDiagram** - Use this when user wants to modify an existing diagram:
|
|
44
|
+
- Keywords: update, modify, change, improve, 更新, 修改, 改进
|
|
45
|
+
- Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图
|
|
46
|
+
- Examples:
|
|
47
|
+
- "Update the diagram to show the new process"
|
|
48
|
+
- "Modify the chart to include more details"
|
|
49
|
+
- "更新架构图"
|
|
50
|
+
|
|
51
|
+
**updateDocument** - Use this for all other content modifications:
|
|
52
|
+
- Text changes, section updates, content improvements
|
|
53
|
+
- No mention of diagrams/images/charts
|
|
54
|
+
- Examples:
|
|
55
|
+
- "Update the introduction section"
|
|
56
|
+
- "Fix the typo in paragraph 2"
|
|
57
|
+
- "Improve the explanation"
|
|
58
|
+
</intent_classification_rules>
|
|
59
|
+
|
|
16
60
|
<user_feedback>
|
|
17
61
|
{{feedback}}
|
|
18
|
-
</user_feedback
|
|
62
|
+
</user_feedback>
|
|
63
|
+
|
|
64
|
+
<analysis_guidelines>
|
|
65
|
+
1. Pay close attention to action verbs (remove, delete, add, update, etc.)
|
|
66
|
+
2. Identify the target object (diagram, image, chart, or general content)
|
|
67
|
+
3. If feedback mentions removing/deleting a diagram/image/chart → deleteDiagram
|
|
68
|
+
4. If feedback mentions adding a diagram/image/chart → addDiagram
|
|
69
|
+
5. If feedback mentions updating a diagram/image/chart → updateDiagram
|
|
70
|
+
6. If feedback is about general content without diagram references → updateDocument
|
|
71
|
+
7. When in doubt, prioritize the most explicit action mentioned in the feedback
|
|
72
|
+
</analysis_guidelines>`;
|
|
19
73
|
const analyzeUpdateFeedbackIntentAgent = AIAgent.from({
|
|
20
74
|
name: "analyzeUpdateFeedbackIntent",
|
|
21
75
|
description:
|
|
@@ -43,11 +97,12 @@ You are a feedback intent analyzer. Your task is to determine which type of cont
|
|
|
43
97
|
return intentType;
|
|
44
98
|
}
|
|
45
99
|
|
|
46
|
-
async function saveDoc(input, options, { content }) {
|
|
100
|
+
async function saveDoc(input, options, { content, intentType }) {
|
|
47
101
|
const saveAgent = options.context?.agents?.["saveDoc"];
|
|
48
102
|
await options.context.invoke(saveAgent, {
|
|
49
103
|
...pick(input, ["path", "docsDir", "labels", "locale"]),
|
|
50
104
|
content,
|
|
105
|
+
intentType, // Pass intentType so saveDoc can handle translation sync
|
|
51
106
|
});
|
|
52
107
|
}
|
|
53
108
|
|
|
@@ -56,42 +111,70 @@ async function addDiagram(input, options) {
|
|
|
56
111
|
const currentContent = contentContext.get();
|
|
57
112
|
const generateDiagramAgent = options.context.agents["checkGenerateDiagram"];
|
|
58
113
|
const generateDiagramResult = await options.context.invoke(generateDiagramAgent, {
|
|
59
|
-
...pick(input, ["locale", "path", "diagramming", "feedback"]),
|
|
114
|
+
...pick(input, ["locale", "path", "docsDir", "diagramming", "feedback"]),
|
|
60
115
|
documentContent: currentContent,
|
|
116
|
+
originalContent: currentContent,
|
|
61
117
|
});
|
|
62
118
|
const content = generateDiagramResult.content;
|
|
63
119
|
contentContext.set(content);
|
|
64
|
-
|
|
120
|
+
// Pass intentType to saveDoc so it can handle translation sync automatically
|
|
121
|
+
await saveDoc(input, options, { content, intentType: "addDiagram" });
|
|
65
122
|
return { content };
|
|
66
123
|
}
|
|
67
124
|
|
|
68
125
|
async function updateDiagram(input, options) {
|
|
69
126
|
const contentContext = userContextAt(options, `currentContents.${input.path}`);
|
|
70
127
|
const currentContent = contentContext.get();
|
|
71
|
-
let [content
|
|
128
|
+
let [content] = replaceD2WithPlaceholder({
|
|
72
129
|
content: currentContent,
|
|
73
130
|
});
|
|
74
131
|
const generateAgent = options.context?.agents?.["generateDiagram"];
|
|
75
|
-
const
|
|
132
|
+
const result = await options.context.invoke(generateAgent, {
|
|
76
133
|
documentContent: content,
|
|
77
134
|
locale: input.locale,
|
|
78
|
-
|
|
135
|
+
diagramming: input.diagramming || {},
|
|
79
136
|
feedback: input.feedback,
|
|
137
|
+
originalContent: currentContent, // Pass original content to find existing diagrams
|
|
138
|
+
path: input.path,
|
|
139
|
+
docsDir: input.docsDir,
|
|
80
140
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
141
|
+
|
|
142
|
+
// generateDiagram now returns { content } with image already inserted
|
|
143
|
+
// The image replaces DIAGRAM_PLACEHOLDER or D2 code blocks
|
|
144
|
+
if (result?.content) {
|
|
145
|
+
content = result.content;
|
|
146
|
+
}
|
|
147
|
+
|
|
85
148
|
contentContext.set(content);
|
|
86
|
-
|
|
149
|
+
// Pass intentType to saveDoc so it can handle translation sync automatically
|
|
150
|
+
await saveDoc(input, options, { content, intentType: "updateDiagram" });
|
|
87
151
|
return { content };
|
|
88
152
|
}
|
|
89
153
|
|
|
90
154
|
async function deleteDiagram(input, options) {
|
|
91
155
|
const contentContext = userContextAt(options, `currentContents.${input.path}`);
|
|
92
156
|
const currentContent = contentContext.get();
|
|
93
|
-
|
|
157
|
+
|
|
158
|
+
// Extract diagram index from feedback if provided
|
|
159
|
+
// This allows deleting a specific diagram when multiple diagrams exist
|
|
160
|
+
let diagramIndex = input.diagramIndex;
|
|
161
|
+
if (diagramIndex === undefined && input.feedback) {
|
|
162
|
+
// Import extractDiagramIndexFromFeedback from replace-d2-with-image.mjs
|
|
163
|
+
const { extractDiagramIndexFromFeedback } = await import(
|
|
164
|
+
"../../create/replace-d2-with-image.mjs"
|
|
165
|
+
);
|
|
166
|
+
const extractedIndex = extractDiagramIndexFromFeedback(input.feedback);
|
|
167
|
+
if (extractedIndex !== null) {
|
|
168
|
+
diagramIndex = extractedIndex;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Replace all diagrams (D2 code blocks, generated images, Mermaid) with placeholder
|
|
173
|
+
// If diagramIndex is provided, only replace that specific diagram
|
|
174
|
+
// This ensures LLM can identify and remove the diagram regardless of its type
|
|
175
|
+
const documentContent = replaceDiagramsWithPlaceholder({
|
|
94
176
|
content: currentContent,
|
|
177
|
+
diagramIndex,
|
|
95
178
|
});
|
|
96
179
|
const instructions = `<role>
|
|
97
180
|
Your task is to remove ${DIAGRAM_PLACEHOLDER} and adjust the document context (based on the user's feedback) to make it easier to understand.
|
|
@@ -123,8 +206,24 @@ Your task is to remove ${DIAGRAM_PLACEHOLDER} and adjust the document context (b
|
|
|
123
206
|
documentContent,
|
|
124
207
|
feedback: input.feedback,
|
|
125
208
|
});
|
|
209
|
+
|
|
210
|
+
// Delete associated diagram image files
|
|
211
|
+
if (input.docsDir) {
|
|
212
|
+
try {
|
|
213
|
+
const { deleteDiagramImages } = await import("../../../utils/delete-diagram-images.mjs");
|
|
214
|
+
const { deleted } = await deleteDiagramImages(currentContent, input.path, input.docsDir);
|
|
215
|
+
if (deleted > 0) {
|
|
216
|
+
console.log(`Deleted ${deleted} diagram image file(s) for ${input.path}`);
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
// Don't fail the operation if image deletion fails
|
|
220
|
+
console.warn(`Failed to delete diagram images: ${error.message}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
126
224
|
contentContext.set(content);
|
|
127
|
-
|
|
225
|
+
// Pass intentType to saveDoc so it can handle translation sync automatically
|
|
226
|
+
await saveDoc(input, options, { content, intentType: "deleteDiagram" });
|
|
128
227
|
|
|
129
228
|
return { content };
|
|
130
229
|
}
|
|
@@ -149,7 +248,19 @@ async function updateDocument(input, options) {
|
|
|
149
248
|
}
|
|
150
249
|
|
|
151
250
|
export default async function updateSingleDocumentDetail(input, options) {
|
|
152
|
-
|
|
251
|
+
// Use intentType from input if available (analyzed in parent flow),
|
|
252
|
+
// otherwise fall back to analyzing it here (for backward compatibility)
|
|
253
|
+
let intentType = input.intentType;
|
|
254
|
+
if (!intentType && input.feedback) {
|
|
255
|
+
intentType = await getIntentType(input, options);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// If intentType is still null or undefined, default to updateDocument
|
|
259
|
+
// This ensures that some operation is always performed, even if intent analysis failed
|
|
260
|
+
// or no explicit intent was provided
|
|
261
|
+
if (!intentType) {
|
|
262
|
+
intentType = "updateDocument";
|
|
263
|
+
}
|
|
153
264
|
|
|
154
265
|
const fnMap = {
|
|
155
266
|
addDiagram,
|
|
@@ -162,5 +273,8 @@ export default async function updateSingleDocumentDetail(input, options) {
|
|
|
162
273
|
const result = await fnMap[intentType](input, options);
|
|
163
274
|
return result;
|
|
164
275
|
}
|
|
165
|
-
|
|
276
|
+
|
|
277
|
+
// Fallback: if intentType is not in fnMap, default to updateDocument
|
|
278
|
+
console.warn(`Unknown intentType: ${intentType}, defaulting to updateDocument`);
|
|
279
|
+
return await updateDocument(input, options);
|
|
166
280
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { AIAgent } from "@aigne/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Analyze user feedback to determine the intent type for document updates
|
|
6
|
+
* Returns one of: "addDiagram", "deleteDiagram", "updateDiagram", "updateDocument"
|
|
7
|
+
* Returns null if feedback is empty or invalid
|
|
8
|
+
*/
|
|
9
|
+
export default async function analyzeFeedbackIntent({ feedback, shouldUpdateDiagrams }, options) {
|
|
10
|
+
// Check if feedback exists and is not empty
|
|
11
|
+
// If feedback is empty and --diagram flag is set, default to updateDiagram
|
|
12
|
+
// Otherwise return null
|
|
13
|
+
if (!feedback || typeof feedback !== "string" || !feedback.trim()) {
|
|
14
|
+
// If --diagram flag is set, default to updateDiagram (user wants to update diagrams)
|
|
15
|
+
if (shouldUpdateDiagrams) {
|
|
16
|
+
return { intentType: "updateDiagram" };
|
|
17
|
+
}
|
|
18
|
+
return { intentType: null };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Always analyze user feedback first, even if --diagram flag is set
|
|
22
|
+
// This ensures user's explicit intent (e.g., "remove image", "delete diagram") is respected
|
|
23
|
+
// The --diagram flag should only be a hint, not override explicit user commands
|
|
24
|
+
|
|
25
|
+
const instructions = `<role>
|
|
26
|
+
You are a feedback intent analyzer. Your task is to determine which type of content modifications are needed based on the user's feedback.
|
|
27
|
+
|
|
28
|
+
You must analyze the user's feedback and classify it into one of the following intent types:
|
|
29
|
+
- addDiagram: User wants to add a new diagram/image/chart
|
|
30
|
+
- deleteDiagram: User wants to remove/delete a diagram/image/chart
|
|
31
|
+
- updateDiagram: User wants to modify/update an existing diagram/image/chart
|
|
32
|
+
- updateDocument: User wants to update document content (text, sections, etc.) without diagram operations
|
|
33
|
+
</role>
|
|
34
|
+
|
|
35
|
+
<intent_classification_rules>
|
|
36
|
+
**deleteDiagram** - Use this when user explicitly wants to remove or delete a diagram/image/chart:
|
|
37
|
+
- Keywords: remove, delete, 删除, 移除, 去掉, 清除
|
|
38
|
+
- Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图, 架构图
|
|
39
|
+
- Examples:
|
|
40
|
+
- "Remove the diagram"
|
|
41
|
+
- "Delete the image"
|
|
42
|
+
- "删除这张图片"
|
|
43
|
+
- "Remove the second diagram"
|
|
44
|
+
- "去掉架构图"
|
|
45
|
+
- "Remove image from page 3"
|
|
46
|
+
- "Delete the chart showing the flow"
|
|
47
|
+
|
|
48
|
+
**addDiagram** - Use this when user wants to add a new diagram:
|
|
49
|
+
- Keywords: add, create, insert, 添加, 创建, 插入
|
|
50
|
+
- Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图
|
|
51
|
+
- Examples:
|
|
52
|
+
- "Add a diagram showing the architecture"
|
|
53
|
+
- "Create a flow chart"
|
|
54
|
+
- "添加一个架构图"
|
|
55
|
+
|
|
56
|
+
**updateDiagram** - Use this when user wants to modify an existing diagram:
|
|
57
|
+
- Keywords: update, modify, change, improve, 更新, 修改, 改进
|
|
58
|
+
- Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图
|
|
59
|
+
- Examples:
|
|
60
|
+
- "Update the diagram to show the new process"
|
|
61
|
+
- "Modify the chart to include more details"
|
|
62
|
+
- "更新架构图"
|
|
63
|
+
|
|
64
|
+
**updateDocument** - Use this for all other content modifications:
|
|
65
|
+
- Text changes, section updates, content improvements
|
|
66
|
+
- No mention of diagrams/images/charts
|
|
67
|
+
- Examples:
|
|
68
|
+
- "Update the introduction section"
|
|
69
|
+
- "Fix the typo in paragraph 2"
|
|
70
|
+
- "Improve the explanation"
|
|
71
|
+
</intent_classification_rules>
|
|
72
|
+
|
|
73
|
+
<user_feedback>
|
|
74
|
+
{{feedback}}
|
|
75
|
+
</user_feedback>
|
|
76
|
+
|
|
77
|
+
<analysis_guidelines>
|
|
78
|
+
1. Pay close attention to action verbs (remove, delete, add, update, etc.)
|
|
79
|
+
2. Identify the target object (diagram, image, chart, or general content)
|
|
80
|
+
3. If feedback mentions removing/deleting a diagram/image/chart → deleteDiagram
|
|
81
|
+
4. If feedback mentions adding a diagram/image/chart → addDiagram
|
|
82
|
+
5. If feedback mentions updating a diagram/image/chart → updateDiagram
|
|
83
|
+
6. If feedback is about general content without diagram references → updateDocument
|
|
84
|
+
7. When in doubt, prioritize the most explicit action mentioned in the feedback
|
|
85
|
+
</analysis_guidelines>`;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const analyzeUpdateFeedbackIntentAgent = AIAgent.from({
|
|
89
|
+
name: "analyzeUpdateFeedbackIntent",
|
|
90
|
+
description:
|
|
91
|
+
"Analyze user feedback to determine if document are needed for content modifications",
|
|
92
|
+
task_render_mode: "hide",
|
|
93
|
+
instructions,
|
|
94
|
+
inputSchema: z.object({
|
|
95
|
+
feedback: z.string().describe("User feedback for content modifications"),
|
|
96
|
+
}),
|
|
97
|
+
outputSchema: z.object({
|
|
98
|
+
intentType: z
|
|
99
|
+
.enum(["addDiagram", "deleteDiagram", "updateDiagram", "updateDocument"])
|
|
100
|
+
.describe(
|
|
101
|
+
"The primary type of user intention: one of addDiagram, deleteDiagram, updateDiagram, updateDocument",
|
|
102
|
+
),
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const { intentType } = await options.context.invoke(analyzeUpdateFeedbackIntentAgent, {
|
|
107
|
+
feedback: feedback.trim(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// If --diagram flag is set and user didn't explicitly request delete/add,
|
|
111
|
+
// default to updateDiagram (for backward compatibility)
|
|
112
|
+
// But if user explicitly requested delete/add, respect that intent
|
|
113
|
+
if (
|
|
114
|
+
shouldUpdateDiagrams &&
|
|
115
|
+
intentType &&
|
|
116
|
+
!["deleteDiagram", "addDiagram"].includes(intentType)
|
|
117
|
+
) {
|
|
118
|
+
return { intentType: "updateDiagram" };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { intentType };
|
|
122
|
+
} catch (error) {
|
|
123
|
+
// If analysis fails and --diagram flag is set, default to updateDiagram
|
|
124
|
+
// Otherwise return null to fall back to default document update flow
|
|
125
|
+
if (shouldUpdateDiagrams) {
|
|
126
|
+
console.warn(
|
|
127
|
+
`Failed to analyze feedback intent, defaulting to updateDiagram due to --diagram flag: ${error.message}`,
|
|
128
|
+
);
|
|
129
|
+
return { intentType: "updateDiagram" };
|
|
130
|
+
}
|
|
131
|
+
console.warn(`Failed to analyze feedback intent: ${error.message}`);
|
|
132
|
+
return { intentType: null };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
analyzeFeedbackIntent.task_render_mode = "hide";
|
|
@@ -5,7 +5,15 @@ import {
|
|
|
5
5
|
getActionText,
|
|
6
6
|
getMainLanguageFiles,
|
|
7
7
|
processSelectedFiles,
|
|
8
|
+
readFileContent,
|
|
8
9
|
} from "../../utils/docs-finder-utils.mjs";
|
|
10
|
+
import {
|
|
11
|
+
hasDiagramContent,
|
|
12
|
+
hasBananaImages,
|
|
13
|
+
getDiagramTypeLabels,
|
|
14
|
+
formatDiagramTypeSuffix,
|
|
15
|
+
} from "../../utils/check-document-has-diagram.mjs";
|
|
16
|
+
import { debug } from "../../utils/debug.mjs";
|
|
9
17
|
import { DOC_ACTION } from "../../utils/constants/index.mjs";
|
|
10
18
|
|
|
11
19
|
function getFeedbackMessage(action) {
|
|
@@ -27,6 +35,10 @@ export default async function chooseDocs(
|
|
|
27
35
|
reset = false,
|
|
28
36
|
requiredFeedback = true,
|
|
29
37
|
action,
|
|
38
|
+
shouldUpdateDiagrams = false,
|
|
39
|
+
shouldAutoSelectDiagrams = false,
|
|
40
|
+
shouldSyncImages = false,
|
|
41
|
+
...rest
|
|
30
42
|
},
|
|
31
43
|
options,
|
|
32
44
|
) {
|
|
@@ -48,55 +60,181 @@ export default async function chooseDocs(
|
|
|
48
60
|
);
|
|
49
61
|
}
|
|
50
62
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const flatName = file.replace(/\.md$/, "").replace(/\.\w+(-\w+)?$/, "");
|
|
55
|
-
const docItem = documentStructure.find((item) => {
|
|
56
|
-
const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
|
|
57
|
-
return itemFlattenedPath === flatName;
|
|
58
|
-
});
|
|
63
|
+
// If --diagram-sync flag is set, filter documents by banana images only
|
|
64
|
+
if (shouldSyncImages) {
|
|
65
|
+
debug("🔄 Filtering documents with banana images...");
|
|
59
66
|
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
// Read content for all files and filter by banana images only
|
|
68
|
+
const filesWithImages = [];
|
|
69
|
+
for (const fileName of mainLanguageFiles) {
|
|
70
|
+
const content = await readFileContent(docsDir, fileName);
|
|
71
|
+
if (content && hasBananaImages(content)) {
|
|
72
|
+
filesWithImages.push(fileName);
|
|
73
|
+
}
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
if (filesWithImages.length === 0) {
|
|
77
|
+
debug("ℹ️ No documents found with banana images (DIAGRAM_IMAGE_START markers).");
|
|
78
|
+
return {
|
|
79
|
+
selectedDocs: [],
|
|
80
|
+
feedback: "",
|
|
81
|
+
selectedPaths: [],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
debug(`✅ Found ${filesWithImages.length} document(s) with banana images.`);
|
|
86
|
+
debug("📋 Auto-selecting all documents with banana images...");
|
|
87
|
+
// Show diagram types for each document
|
|
88
|
+
for (const file of filesWithImages) {
|
|
89
|
+
const content = await readFileContent(docsDir, file);
|
|
90
|
+
const diagramLabels = content ? getDiagramTypeLabels(content) : [];
|
|
91
|
+
const diagramSuffix = formatDiagramTypeSuffix(diagramLabels);
|
|
92
|
+
debug(` • ${file}${diagramSuffix}`);
|
|
93
|
+
}
|
|
94
|
+
selectedFiles = filesWithImages;
|
|
95
|
+
}
|
|
96
|
+
// If --diagram flag is set, filter documents by diagram content
|
|
97
|
+
else if (shouldUpdateDiagrams) {
|
|
98
|
+
debug("🔄 Filtering documents with diagram content...");
|
|
73
99
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
return choices.filter((choice) => choice.name.toLowerCase().includes(term.toLowerCase()));
|
|
81
|
-
},
|
|
82
|
-
validate: (answer) => {
|
|
83
|
-
if (answer.length === 0) {
|
|
84
|
-
return "Please select at least one document";
|
|
100
|
+
// Read content for all files and filter by diagram content
|
|
101
|
+
const filesWithDiagrams = [];
|
|
102
|
+
for (const fileName of mainLanguageFiles) {
|
|
103
|
+
const content = await readFileContent(docsDir, fileName);
|
|
104
|
+
if (content && hasDiagramContent(content)) {
|
|
105
|
+
filesWithDiagrams.push(fileName);
|
|
85
106
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (filesWithDiagrams.length === 0) {
|
|
110
|
+
debug(
|
|
111
|
+
"ℹ️ No documents found with diagram content (d2 code blocks, placeholders, or diagram images).",
|
|
112
|
+
);
|
|
113
|
+
return {
|
|
114
|
+
selectedDocs: [],
|
|
115
|
+
feedback: "",
|
|
116
|
+
selectedPaths: [],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
debug(`✅ Found ${filesWithDiagrams.length} document(s) with diagram content.`);
|
|
121
|
+
|
|
122
|
+
// If --diagram-all, auto-select all; otherwise let user choose
|
|
123
|
+
if (shouldAutoSelectDiagrams) {
|
|
124
|
+
debug("📋 Auto-selecting all documents with diagrams...");
|
|
125
|
+
// Show diagram types for each document in auto-select mode
|
|
126
|
+
for (const file of filesWithDiagrams) {
|
|
127
|
+
const content = await readFileContent(docsDir, file);
|
|
128
|
+
const diagramLabels = content ? getDiagramTypeLabels(content) : [];
|
|
129
|
+
const diagramSuffix = formatDiagramTypeSuffix(diagramLabels);
|
|
130
|
+
debug(` • ${file}${diagramSuffix}`);
|
|
131
|
+
}
|
|
132
|
+
selectedFiles = filesWithDiagrams;
|
|
133
|
+
} else {
|
|
134
|
+
// --diagram mode: show only documents with diagrams, let user select
|
|
135
|
+
const choices = await Promise.all(
|
|
136
|
+
filesWithDiagrams.map(async (file) => {
|
|
137
|
+
// Convert filename to flat path to find corresponding documentation structure item
|
|
138
|
+
const flatName = file.replace(/\.md$/, "").replace(/\.\w+(-\w+)?$/, "");
|
|
139
|
+
const docItem = documentStructure.find((item) => {
|
|
140
|
+
const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
|
|
141
|
+
return itemFlattenedPath === flatName;
|
|
142
|
+
});
|
|
89
143
|
|
|
90
|
-
|
|
91
|
-
|
|
144
|
+
// Read content to detect diagram types
|
|
145
|
+
const content = await readFileContent(docsDir, file);
|
|
146
|
+
const diagramLabels = content ? getDiagramTypeLabels(content) : [];
|
|
147
|
+
const diagramSuffix = formatDiagramTypeSuffix(diagramLabels);
|
|
148
|
+
|
|
149
|
+
// Use title if available, otherwise fall back to filename
|
|
150
|
+
let displayName = docItem?.title;
|
|
151
|
+
if (displayName) {
|
|
152
|
+
displayName = `${displayName} (${file})${diagramSuffix}`;
|
|
153
|
+
} else {
|
|
154
|
+
displayName = `${file}${diagramSuffix}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
name: displayName,
|
|
159
|
+
value: file,
|
|
160
|
+
};
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Let user select multiple files from filtered list
|
|
165
|
+
selectedFiles = await options.prompts.checkbox({
|
|
166
|
+
message: getActionText("Select documents with diagrams to {action}:", docAction),
|
|
167
|
+
source: (term) => {
|
|
168
|
+
if (!term) return choices;
|
|
169
|
+
|
|
170
|
+
return choices.filter((choice) =>
|
|
171
|
+
choice.name.toLowerCase().includes(term.toLowerCase()),
|
|
172
|
+
);
|
|
173
|
+
},
|
|
174
|
+
validate: (answer) => {
|
|
175
|
+
if (answer.length === 0) {
|
|
176
|
+
return "Please select at least one document";
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (!selectedFiles || selectedFiles.length === 0) {
|
|
183
|
+
throw new Error("No documents selected");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
// Normal flow: let user select documents from all files
|
|
188
|
+
// Convert files to choices with titles
|
|
189
|
+
const choices = mainLanguageFiles.map((file) => {
|
|
190
|
+
// Convert filename to flat path to find corresponding documentation structure item
|
|
191
|
+
const flatName = file.replace(/\.md$/, "").replace(/\.\w+(-\w+)?$/, "");
|
|
192
|
+
const docItem = documentStructure.find((item) => {
|
|
193
|
+
const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
|
|
194
|
+
return itemFlattenedPath === flatName;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Use title if available, otherwise fall back to filename
|
|
198
|
+
let displayName = docItem?.title;
|
|
199
|
+
if (displayName) {
|
|
200
|
+
displayName = `${displayName} (${file})`;
|
|
201
|
+
} else {
|
|
202
|
+
displayName = file;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
name: displayName,
|
|
207
|
+
value: file,
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Let user select multiple files
|
|
212
|
+
selectedFiles = await options.prompts.checkbox({
|
|
213
|
+
message: getActionText("Select documents to {action}:", docAction),
|
|
214
|
+
source: (term) => {
|
|
215
|
+
if (!term) return choices;
|
|
216
|
+
|
|
217
|
+
return choices.filter((choice) =>
|
|
218
|
+
choice.name.toLowerCase().includes(term.toLowerCase()),
|
|
219
|
+
);
|
|
220
|
+
},
|
|
221
|
+
validate: (answer) => {
|
|
222
|
+
if (answer.length === 0) {
|
|
223
|
+
return "Please select at least one document";
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (!selectedFiles || selectedFiles.length === 0) {
|
|
230
|
+
throw new Error("No documents selected");
|
|
231
|
+
}
|
|
92
232
|
}
|
|
93
233
|
|
|
94
234
|
// Process selected files and convert to found items
|
|
95
235
|
foundItems = await processSelectedFiles(selectedFiles, documentStructure, docsDir);
|
|
96
236
|
} catch (error) {
|
|
97
|
-
|
|
98
|
-
getActionText(`\nFailed to select documents to {action}: ${error.message}`, docAction),
|
|
99
|
-
);
|
|
237
|
+
debug(getActionText(`\nFailed to select documents to {action}: ${error.message}`, docAction));
|
|
100
238
|
process.exit(0);
|
|
101
239
|
}
|
|
102
240
|
} else {
|
|
@@ -105,7 +243,7 @@ export default async function chooseDocs(
|
|
|
105
243
|
const foundItem = await findItemByPath(documentStructure, docPath, boardId, docsDir, locale);
|
|
106
244
|
|
|
107
245
|
if (!foundItem) {
|
|
108
|
-
|
|
246
|
+
debug(`⚠️ Item with path "${docPath}" not found in documentStructure`);
|
|
109
247
|
continue;
|
|
110
248
|
}
|
|
111
249
|
|
|
@@ -120,8 +258,15 @@ export default async function chooseDocs(
|
|
|
120
258
|
}
|
|
121
259
|
|
|
122
260
|
// Prompt for feedback if not provided
|
|
261
|
+
// Skip feedback prompt if --diagram, --diagram-all, or --diagram-sync flag is set
|
|
123
262
|
let userFeedback = feedback;
|
|
124
|
-
if (
|
|
263
|
+
if (
|
|
264
|
+
!userFeedback &&
|
|
265
|
+
(requiredFeedback || foundItems?.length > 1) &&
|
|
266
|
+
!shouldUpdateDiagrams &&
|
|
267
|
+
!shouldSyncImages &&
|
|
268
|
+
!rest.isChat
|
|
269
|
+
) {
|
|
125
270
|
const feedbackMessage = getFeedbackMessage(docAction);
|
|
126
271
|
|
|
127
272
|
userFeedback = await options.prompts.input({
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditionally call generateDocument. If we already have content for diagram-only intents,
|
|
3
|
+
* skip LLM generation and pass through the existing content.
|
|
4
|
+
*/
|
|
5
|
+
export default async function generateDocumentOrSkip(input, options) {
|
|
6
|
+
const { intentType, content, skipGenerateDocument } = input;
|
|
7
|
+
|
|
8
|
+
const isDiagramIntent =
|
|
9
|
+
intentType && ["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType);
|
|
10
|
+
const shouldSkip = Boolean(skipGenerateDocument || (isDiagramIntent && content));
|
|
11
|
+
|
|
12
|
+
if (shouldSkip) {
|
|
13
|
+
// Return the existing content and mark the generation as skipped
|
|
14
|
+
return {
|
|
15
|
+
...input,
|
|
16
|
+
content,
|
|
17
|
+
documentContent: content,
|
|
18
|
+
originalContent: content,
|
|
19
|
+
reviewContent: content,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const generateAgent = options.context?.agents?.["generateDocument"];
|
|
24
|
+
if (!generateAgent) {
|
|
25
|
+
throw new Error("generateDocument agent not found");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await options.context.invoke(generateAgent, input);
|
|
29
|
+
const generatedContent = result?.content ?? result;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
...input,
|
|
33
|
+
...result,
|
|
34
|
+
content: generatedContent,
|
|
35
|
+
documentContent: generatedContent,
|
|
36
|
+
originalContent: generatedContent,
|
|
37
|
+
reviewContent: generatedContent,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
generateDocumentOrSkip.task_render_mode = "hide";
|