@aigne/doc-smith 0.9.9-beta → 0.9.9-beta.1

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/agents/create/aggregate-document-structure.mjs +21 -0
  3. package/agents/create/analyze-diagram-type-llm.yaml +1 -2
  4. package/agents/create/analyze-diagram-type.mjs +160 -2
  5. package/agents/create/generate-diagram-image.yaml +31 -0
  6. package/agents/create/generate-structure.yaml +1 -12
  7. package/agents/create/replace-d2-with-image.mjs +12 -27
  8. package/agents/create/utils/merge-document-structures.mjs +9 -3
  9. package/agents/localize/index.yaml +4 -0
  10. package/agents/localize/save-doc-translation-or-skip.mjs +18 -0
  11. package/agents/localize/set-review-content.mjs +58 -0
  12. package/agents/localize/translate-diagram.yaml +62 -0
  13. package/agents/localize/translate-document-wrapper.mjs +34 -0
  14. package/agents/localize/translate-multilingual.yaml +15 -9
  15. package/agents/localize/translate-or-skip-diagram.mjs +52 -0
  16. package/agents/publish/translate-meta.mjs +58 -6
  17. package/agents/update/generate-diagram.yaml +25 -8
  18. package/agents/update/index.yaml +1 -8
  19. package/agents/update/save-and-translate-document.mjs +5 -1
  20. package/agents/update/update-single/update-single-document-detail.mjs +52 -10
  21. package/agents/utils/analyze-feedback-intent.mjs +197 -80
  22. package/agents/utils/check-detail-result.mjs +14 -1
  23. package/agents/utils/choose-docs.mjs +3 -43
  24. package/agents/utils/save-doc-translation.mjs +2 -33
  25. package/agents/utils/save-doc.mjs +3 -37
  26. package/aigne.yaml +2 -2
  27. package/package.json +1 -1
  28. package/prompts/detail/diagram/generate-image-user.md +49 -0
  29. package/utils/d2-utils.mjs +10 -3
  30. package/utils/delete-diagram-images.mjs +3 -3
  31. package/utils/diagram-version-utils.mjs +14 -0
  32. package/utils/image-compress.mjs +1 -1
  33. package/utils/sync-diagram-to-translations.mjs +3 -3
  34. package/utils/translate-diagram-images.mjs +790 -0
  35. package/agents/update/check-sync-image-flag.mjs +0 -55
  36. package/agents/update/sync-images-and-exit.mjs +0 -148
@@ -1,7 +1,10 @@
1
1
  type: team
2
2
  name: translateMultilingual
3
- description: Batch translate documents to multiple languages
3
+ description: Batch translate documents to multiple languages. Use --diagram to translate only diagram images without translating document content.
4
4
  skills:
5
+ # Step 1: Check --diagram flag and determine if we should translate diagrams only
6
+ - url: ./translate-or-skip-diagram.mjs
7
+ # Translate document content (only if translation is not already set)
5
8
  - type: team
6
9
  task_render_mode: collapse
7
10
  name: translate
@@ -9,20 +12,20 @@ skills:
9
12
  - url: ../utils/find-user-preferences-by-path.mjs
10
13
  default_input:
11
14
  scope: translation
12
- - ../localize/translate-document.yaml
13
- - type: transform
14
- jsonata: |
15
- $merge([
16
- $,
17
- { "reviewContent": translation }
18
- ])
15
+ # Step 2: Cache diagram images for translation (before document translation)
16
+ # Checks --diagram flag and timestamps to determine if images need translation
17
+ - url: ../../utils/translate-diagram-images.mjs
18
+ # Step 3: Translate document content
19
+ - url: ./translate-document-wrapper.mjs
20
+ # Step 4: Replace cached diagram images and set review content
21
+ - url: ./set-review-content.mjs
19
22
  reflection:
20
23
  reviewer: ../utils/check-detail-result.mjs
21
24
  is_approved: isApproved
22
25
  max_iterations: 5
23
26
  return_last_on_max_iterations: true
24
27
  task_title: Translate '{{ title }}' to '{{ language }}'
25
- - ../utils/save-doc-translation.mjs
28
+ - url: ./save-doc-translation-or-skip.mjs
26
29
  input_schema:
27
30
  type: object
28
31
  properties:
@@ -35,6 +38,9 @@ input_schema:
35
38
  type: string
36
39
  content:
37
40
  type: string
41
+ diagram:
42
+ type: boolean
43
+ description: Translate only diagram images without translating document content
38
44
  output_schema:
39
45
  type: object
40
46
  properties:
@@ -0,0 +1,52 @@
1
+ import { debug } from "../../utils/debug.mjs";
2
+
3
+ /**
4
+ * Check --diagram flag and conditionally translate document
5
+ * If --diagram is set, skip document translation and only translate images
6
+ * Otherwise, proceed with normal document translation
7
+ */
8
+ export default async function translateOrSkipDiagram(input) {
9
+ // Check if --diagram flag is set
10
+ let shouldTranslateDiagramsOnly = false;
11
+
12
+ if (process.argv) {
13
+ const hasDiagramFlag = process.argv.some((arg) => arg === "--diagram" || arg === "-d");
14
+ if (hasDiagramFlag) {
15
+ shouldTranslateDiagramsOnly = true;
16
+ }
17
+ }
18
+
19
+ if (input.diagram === true || input.diagram === "true") {
20
+ shouldTranslateDiagramsOnly = true;
21
+ }
22
+
23
+ if (
24
+ process.env.DOC_SMITH_TRANSLATE_DIAGRAMS_ONLY === "true" ||
25
+ process.env.DOC_SMITH_TRANSLATE_DIAGRAMS_ONLY === "1"
26
+ ) {
27
+ shouldTranslateDiagramsOnly = true;
28
+ }
29
+
30
+ // If --diagram flag is set, skip document translation
31
+ if (shouldTranslateDiagramsOnly) {
32
+ debug("⏭️ --diagram flag set: skipping document translation, only translating images");
33
+ // Set translation to content to skip actual translation
34
+ // Also set isApproved to true to skip reflection check
35
+ return {
36
+ ...input,
37
+ shouldTranslateDiagramsOnly: true,
38
+ translation: input.content || "",
39
+ reviewContent: input.content || "",
40
+ isApproved: true, // Skip reflection check
41
+ };
42
+ }
43
+
44
+ // Otherwise, proceed with normal translation
45
+ // Don't call translateDocument here - let translate-document-wrapper.mjs handle it
46
+ return {
47
+ ...input,
48
+ shouldTranslateDiagramsOnly: false,
49
+ };
50
+ }
51
+
52
+ translateOrSkipDiagram.task_render_mode = "hide";
@@ -6,6 +6,8 @@ import { parse as yamlParse, stringify as yamlStringify } from "yaml";
6
6
  import z from "zod";
7
7
 
8
8
  import { DOC_SMITH_DIR } from "../../utils/constants/index.mjs";
9
+ import { loadDocumentStructure } from "../../utils/docs-finder-utils.mjs";
10
+ import { saveValueToConfig } from "../../utils/utils.mjs";
9
11
 
10
12
  export default async function translateMeta(
11
13
  { projectName, projectDesc, locale, translateLanguages = [] },
@@ -13,6 +15,41 @@ export default async function translateMeta(
13
15
  ) {
14
16
  const languages = [...new Set([...(locale ? [locale] : []), ...(translateLanguages || [])])];
15
17
 
18
+ // If projectDesc is empty, first try to load overview.md, then fallback to structure-plan.json
19
+ let finalProjectDesc = projectDesc;
20
+ if (!finalProjectDesc || finalProjectDesc.trim() === "") {
21
+ // First, try to read overview.md
22
+ const overviewFilePath = join(DOC_SMITH_DIR, "docs", "overview.md");
23
+ const overviewExists = await fs.pathExists(overviewFilePath);
24
+
25
+ if (overviewExists) {
26
+ try {
27
+ const overviewContent = await fs.readFile(overviewFilePath, "utf-8");
28
+ finalProjectDesc = overviewContent;
29
+ } catch {
30
+ // If reading fails, fallback to structure-plan.json
31
+ finalProjectDesc = "";
32
+ }
33
+ } else {
34
+ try {
35
+ const outputDir = join(DOC_SMITH_DIR, "output");
36
+ const documentStructure = await loadDocumentStructure(outputDir);
37
+ if (documentStructure && Array.isArray(documentStructure)) {
38
+ const overviewItem =
39
+ documentStructure.find(
40
+ (item) => item.title === "Overview" || item.path === "/overview",
41
+ ) || documentStructure[0];
42
+ if (overviewItem?.description) {
43
+ finalProjectDesc = overviewItem.description;
44
+ }
45
+ }
46
+ } catch {
47
+ // If structure-plan.json doesn't exist or parsing fails, keep empty desc
48
+ finalProjectDesc = "";
49
+ }
50
+ }
51
+ }
52
+
16
53
  const translationCacheFilePath = join(DOC_SMITH_DIR, "translation-cache.yaml");
17
54
  await fs.ensureFile(translationCacheFilePath);
18
55
  const translationCache = await fs.readFile(translationCacheFilePath, "utf-8");
@@ -43,15 +80,19 @@ export default async function translateMeta(
43
80
  inputKey: "message",
44
81
  outputSchema: z.object({
45
82
  title: titleTranslationSchema.describe("Translated titles with language codes as keys"),
46
- desc: descTranslationSchema.describe("Translated descriptions with language codes as keys"),
83
+ desc: descTranslationSchema.describe(
84
+ "Translated descriptions with language codes as keys. Each description MUST be within 100 characters.",
85
+ ),
47
86
  }),
48
87
  });
49
88
  if (titleLanguages.length > 0 || descLanguages.length > 0) {
50
89
  const translatedMetadata = await options.context.invoke(agent, {
51
90
  message: `Translate the following title and description into all target languages except the source language. Provide the translations in a JSON object with the language codes as keys. If the project title or description is empty, return an empty string for that field.
52
91
 
92
+ **IMPORTANT**: The description translations MUST be concise and within 100 characters. If the source description is long, extract and translate only the key points or create a brief summary that captures the essence.
93
+
53
94
  Project Title: ${projectName || ""}
54
- Project Description: ${projectDesc || ""}
95
+ Project Description: ${finalProjectDesc || ""}
55
96
 
56
97
  Target Languages: { title: ${titleLanguages.join(", ")}, desc: ${descLanguages.join(", ")} }
57
98
  Source Language: ${locale}
@@ -64,12 +105,18 @@ Respond with a JSON object in the following format:
64
105
  ...
65
106
  },
66
107
  "desc": {
67
- "fr": "Translated Project Description in French",
68
- "es": "Translated Project Description in Spanish",
108
+ "fr": "Translated Project Description in French (max 100 characters)",
109
+ "es": "Translated Project Description in Spanish (max 100 characters)",
69
110
  ...
70
111
  }
71
112
  }
72
113
 
114
+ **Requirements for description translations:**
115
+ - Each description MUST be 100 characters or less
116
+ - Be concise and capture the core essence of the project
117
+ - Use natural, fluent language appropriate for the target culture
118
+ - If the source is very long, create a brief summary instead of a full translation
119
+
73
120
  If no translation is needed, respond with:
74
121
  {
75
122
  "title": {},
@@ -87,17 +134,22 @@ If no translation is needed, respond with:
87
134
  }
88
135
  });
89
136
  }
137
+
138
+ if (!projectDesc && finalProjectDesc) {
139
+ await saveValueToConfig("projectDesc", finalProjectDesc, "Project description");
140
+ }
141
+
90
142
  const saveResult = {
91
143
  ...parsedTranslationCache,
92
144
  [projectName]: titleTranslation,
93
- [projectDesc]: descTranslation,
145
+ [finalProjectDesc]: descTranslation,
94
146
  };
95
147
  await fs.writeFile(translationCacheFilePath, yamlStringify(saveResult), { encoding: "utf8" });
96
148
 
97
149
  return {
98
150
  translatedMetadata: {
99
151
  title: saveResult[projectName] || {},
100
- desc: saveResult[projectDesc] || {},
152
+ desc: saveResult[finalProjectDesc] || {},
101
153
  },
102
154
  };
103
155
  }
@@ -4,14 +4,17 @@ name: generateDiagram
4
4
  skills:
5
5
  # Step 1: Analyze document content to determine diagram type and style
6
6
  - ../create/analyze-diagram-type.mjs
7
- # Step 2: Transform output for image generation agent (map aspectRatio to ratio, add size)
7
+ # Step 2: Transform output for image generation agent (map aspectRatio to ratio, add size, pass existingImage and feedback)
8
8
  - type: transform
9
9
  jsonata: |
10
10
  $merge([
11
11
  $,
12
12
  {
13
13
  "ratio": aspectRatio,
14
- "size": "1K"
14
+ "size": "1K",
15
+ "existingImage": existingImage,
16
+ "useImageToImage": useImageToImage,
17
+ "feedback": feedback
15
18
  }
16
19
  ])
17
20
  # Step 3: Generate diagram image directly with unified agent
@@ -39,18 +42,32 @@ input_schema:
39
42
  type: string
40
43
  description: User feedback that may contain style or type preferences, or diagram index (e.g., 'use anthropomorphic style', 'update the second diagram')
41
44
  default: ""
42
- originalContent:
43
- type: string
44
- description: Original document content before modifications. Used to find existing diagrams when updating (may contain DIAGRAM_IMAGE_START, ```d2, or ```mermaid code blocks)
45
- diagramIndex:
46
- type: number
47
- description: Index of the diagram to replace (0-based). If not provided, will try to extract from feedback.
48
45
  path:
49
46
  type: string
50
47
  description: Document path (e.g., "guides/getting-started.md") used for generating image filename
51
48
  docsDir:
52
49
  type: string
53
50
  description: Documentation directory where assets will be saved (relative to project root)
51
+ intentAnalysis:
52
+ type: object
53
+ description: Analysis results from analyzeFeedbackIntent containing intentType, diagramInfo, generationMode, and changes
54
+ properties:
55
+ intentType:
56
+ type: string
57
+ enum: ["addDiagram", "updateDiagram", "deleteDiagram", "updateDocument"]
58
+ diagramInfo:
59
+ type: object
60
+ nullable: true
61
+ properties:
62
+ path: { type: "string" }
63
+ index: { type: "number" }
64
+ markdown: { type: "string" }
65
+ generationMode:
66
+ type: string
67
+ enum: ["image-to-image", "text-only", "add-new", "remove-image"]
68
+ changes:
69
+ type: array
70
+ items: { type: "string" }
54
71
  required:
55
72
  - documentContent
56
73
  output_schema:
@@ -2,7 +2,7 @@ type: team
2
2
  name: update
3
3
  alias:
4
4
  - up
5
- description: Update specific documents and their translations. Use --diagram to filter and select documents with diagrams, --diagram-all to auto-update all diagrams, or --diagram-sync to sync existing images to translations.
5
+ description: Update specific documents and their translations. Use --diagram to filter and select documents with diagrams, or --diagram-all to auto-update all diagrams.
6
6
  skills:
7
7
  - url: ../init/index.mjs
8
8
  default_input:
@@ -21,16 +21,12 @@ skills:
21
21
  ])
22
22
  # Check if --diagram or --diagram-all flag is set
23
23
  - url: ../update/check-diagram-flag.mjs
24
- # Check if --diagram-sync flag is set
25
- - url: ../update/check-sync-image-flag.mjs
26
24
  - url: ../utils/choose-docs.mjs
27
25
  default_input:
28
26
  requiredFeedback: false
29
27
  - ../utils/format-document-structure.mjs
30
28
  - ../utils/ensure-document-icons.mjs
31
29
  - ../media/load-media-description.mjs
32
- - ../utils/analyze-feedback-intent.mjs
33
- - ../update/sync-images-and-exit.mjs
34
30
  - ../update/check-update-is-single.mjs
35
31
  - ../update/save-and-translate-document.mjs
36
32
  - url: ../utils/action-success.mjs
@@ -59,9 +55,6 @@ input_schema:
59
55
  "diagram-all":
60
56
  type: ["boolean", "string"]
61
57
  description: "Flag to auto-select all documents with diagrams (can also use --diagram-all CLI arg)"
62
- "diagram-sync":
63
- type: ["boolean", "string"]
64
- description: "Flag to sync existing banana images to translations (can also use --diagram-sync CLI arg or DOC_SMITH_SYNC_IMAGES env var)"
65
58
  output_schema:
66
59
  type: object
67
60
  properties:
@@ -58,12 +58,16 @@ export default async function saveAndTranslateDocument(input, options) {
58
58
  // Clear feedback to ensure translation is not affected by update feedback
59
59
  doc.feedback = "";
60
60
 
61
+ // Extract input properties excluding diagram to avoid passing invalid value
62
+ const { diagram, ...inputWithoutDiagram } = input;
63
+
61
64
  await options.context.invoke(translateAgent, {
62
- ...input, // context is required
65
+ ...inputWithoutDiagram, // context is required (without diagram)
63
66
  content: doc.content,
64
67
  title: doc.title,
65
68
  path: doc.path,
66
69
  docsDir,
70
+ diagram: `${diagram}` === "true",
67
71
  });
68
72
  } catch (error) {
69
73
  console.error(`❌ Failed to translate document ${doc.path}:`, error.message);
@@ -9,6 +9,25 @@ import {
9
9
  import { userContextAt } from "../../../utils/utils.mjs";
10
10
 
11
11
  async function getIntentType(input, options) {
12
+ // Single document mode: Get current document content and perform full analysis
13
+ // This is called AFTER userReviewDocument has collected the feedback
14
+ const analyzeFeedbackIntentAgent = options.context?.agents?.["analyzeFeedbackIntent"];
15
+ if (analyzeFeedbackIntentAgent) {
16
+ const contentContext = userContextAt(options, `currentContents.${input.path}`);
17
+ const currentContent = contentContext.get();
18
+
19
+ const result = await options.context.invoke(analyzeFeedbackIntentAgent, {
20
+ feedback: input.feedback,
21
+ documentContent: currentContent,
22
+ shouldUpdateDiagrams: false,
23
+ });
24
+
25
+ return result;
26
+ }
27
+
28
+ console.warn("[getIntentType] analyzeFeedbackIntent agent not found, using fallback");
29
+
30
+ // Fallback to old method if analyzeFeedbackIntent agent not available
12
31
  const instructions = `<role>
13
32
  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
33
 
@@ -113,7 +132,7 @@ async function addDiagram(input, options) {
113
132
  const generateDiagramResult = await options.context.invoke(generateDiagramAgent, {
114
133
  ...pick(input, ["locale", "path", "docsDir", "diagramming", "feedback"]),
115
134
  documentContent: currentContent,
116
- originalContent: currentContent,
135
+ intentAnalysis: input.intentAnalysis, // Pass intent analysis from first layer
117
136
  });
118
137
  const content = generateDiagramResult.content;
119
138
  contentContext.set(content);
@@ -134,9 +153,9 @@ async function updateDiagram(input, options) {
134
153
  locale: input.locale,
135
154
  diagramming: input.diagramming || {},
136
155
  feedback: input.feedback,
137
- originalContent: currentContent, // Pass original content to find existing diagrams
138
156
  path: input.path,
139
157
  docsDir: input.docsDir,
158
+ intentAnalysis: input.intentAnalysis, // Pass intent analysis from first layer
140
159
  });
141
160
 
142
161
  // generateDiagram now returns { content } with image already inserted
@@ -248,18 +267,42 @@ async function updateDocument(input, options) {
248
267
  }
249
268
 
250
269
  export default async function updateSingleDocumentDetail(input, options) {
251
- // Use intentType from input if available (analyzed in parent flow),
252
- // otherwise fall back to analyzing it here (for backward compatibility)
270
+ // Get intent analysis (may include full analysis result with diagramInfo, generationMode, etc.)
271
+ // Note: This is called AFTER userReviewDocument has collected the feedback
272
+ let intentAnalysis = input.intentAnalysis;
253
273
  let intentType = input.intentType;
254
- if (!intentType && input.feedback) {
255
- intentType = await getIntentType(input, options);
274
+
275
+ // If intentAnalysis not provided, analyze it here (with feedback from userReviewDocument)
276
+ if (!intentAnalysis && input.feedback) {
277
+ const analysisResult = await getIntentType(input, options);
278
+
279
+ // Check if result is the new format (with diagramInfo) or old format (just intentType)
280
+ if (analysisResult && typeof analysisResult === "object" && "intentType" in analysisResult) {
281
+ intentAnalysis = analysisResult;
282
+ intentType = analysisResult.intentType;
283
+ } else {
284
+ // Old format: just intentType string
285
+ intentType = analysisResult;
286
+ intentAnalysis = {
287
+ intentType: analysisResult,
288
+ diagramInfo: null,
289
+ generationMode: null,
290
+ changes: [],
291
+ };
292
+ }
256
293
  }
257
294
 
258
295
  // 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
296
  if (!intentType) {
262
297
  intentType = "updateDocument";
298
+ if (!intentAnalysis) {
299
+ intentAnalysis = {
300
+ intentType: "updateDocument",
301
+ diagramInfo: null,
302
+ generationMode: null,
303
+ changes: [],
304
+ };
305
+ }
263
306
  }
264
307
 
265
308
  const fnMap = {
@@ -270,8 +313,7 @@ export default async function updateSingleDocumentDetail(input, options) {
270
313
  };
271
314
 
272
315
  if (fnMap[intentType]) {
273
- const result = await fnMap[intentType](input, options);
274
- return result;
316
+ return await fnMap[intentType]({ ...input, intentAnalysis }, options);
275
317
  }
276
318
 
277
319
  // Fallback: if intentType is not in fnMap, default to updateDocument