@aigne/doc-smith 0.9.8-beta → 0.9.8
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 +9 -0
- package/agents/create/replace-d2-with-image.mjs +12 -11
- package/agents/utils/save-doc-translation.mjs +34 -0
- package/package.json +1 -1
- package/prompts/detail/diagram/generate-image-user.md +1 -1
- package/utils/check-document-has-diagram.mjs +4 -6
- package/utils/d2-utils.mjs +19 -11
- package/utils/delete-diagram-images.mjs +3 -7
- package/utils/sync-diagram-to-translations.mjs +20 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.8](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.8-beta.1...v0.9.8) (2025-12-07)
|
|
4
|
+
|
|
5
|
+
## [0.9.8-beta.1](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.8-beta...v0.9.8-beta.1) (2025-12-06)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **translation:** unify expressions and default to English for image info ([#349](https://github.com/AIGNE-io/aigne-doc-smith/issues/349)) ([c134b1a](https://github.com/AIGNE-io/aigne-doc-smith/commit/c134b1a0f38f5b7daf382b24cf5f71ecd2cccae7))
|
|
11
|
+
|
|
3
12
|
## [0.9.8-beta](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.7...v0.9.8-beta) (2025-12-05)
|
|
4
13
|
|
|
5
14
|
|
|
@@ -2,7 +2,12 @@ import { copyFile, readFile, stat } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import fs from "fs-extra";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
DIAGRAM_PLACEHOLDER,
|
|
7
|
+
d2CodeBlockRegex,
|
|
8
|
+
diagramImageBlockRegex,
|
|
9
|
+
ensureTmpDir,
|
|
10
|
+
} from "../../utils/d2-utils.mjs";
|
|
6
11
|
import { DOC_SMITH_DIR, TMP_DIR, TMP_ASSETS_DIR } from "../../utils/constants/index.mjs";
|
|
7
12
|
import { getContentHash, getFileName } from "../../utils/utils.mjs";
|
|
8
13
|
import { getExtnameFromContentType } from "../../utils/file-utils.mjs";
|
|
@@ -418,40 +423,36 @@ function findAllDiagramLocations(content) {
|
|
|
418
423
|
// 2. Find DIAGRAM_IMAGE_START markers
|
|
419
424
|
// Format: <!-- DIAGRAM_IMAGE_START:type:aspectRatio -->...<!-- DIAGRAM_IMAGE_END -->
|
|
420
425
|
// Note: aspectRatio can contain colon (e.g., "16:9"), so we need to match until -->
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
while (match !== null) {
|
|
426
|
+
const imageMatches = Array.from(content.matchAll(diagramImageBlockRegex));
|
|
427
|
+
for (const match of imageMatches) {
|
|
424
428
|
locations.push({
|
|
425
429
|
type: "image",
|
|
426
430
|
start: match.index,
|
|
427
431
|
end: match.index + match[0].length,
|
|
428
432
|
});
|
|
429
|
-
match = diagramImageRegex.exec(content);
|
|
430
433
|
}
|
|
431
434
|
|
|
432
435
|
// 3. Find D2 code blocks
|
|
433
436
|
// Note: .* matches title or other text after ```d2 (e.g., ```d2 Vault 驗證流程)
|
|
434
|
-
|
|
435
|
-
|
|
437
|
+
const d2Matches = Array.from(content.matchAll(d2CodeBlockRegex));
|
|
438
|
+
for (const match of d2Matches) {
|
|
436
439
|
locations.push({
|
|
437
440
|
type: "d2",
|
|
438
441
|
start: match.index,
|
|
439
442
|
end: match.index + match[0].length,
|
|
440
443
|
});
|
|
441
|
-
match = d2CodeBlockRegex.exec(content);
|
|
442
444
|
}
|
|
443
445
|
|
|
444
446
|
// 4. Find Mermaid code blocks
|
|
445
447
|
// Note: .* matches title or other text after ```mermaid (e.g., ```mermaid Flow Chart)
|
|
446
448
|
const mermaidCodeBlockRegex = /```mermaid.*\n([\s\S]*?)```/g;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
+
const mermaidMatches = Array.from(content.matchAll(mermaidCodeBlockRegex));
|
|
450
|
+
for (const match of mermaidMatches) {
|
|
449
451
|
locations.push({
|
|
450
452
|
type: "mermaid",
|
|
451
453
|
start: match.index,
|
|
452
454
|
end: match.index + match[0].length,
|
|
453
455
|
});
|
|
454
|
-
match = mermaidCodeBlockRegex.exec(content);
|
|
455
456
|
}
|
|
456
457
|
|
|
457
458
|
// Sort by position in document (top to bottom)
|
|
@@ -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 };
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ Please follow **all global rules, styles, aspect ratio logic, and diagram-type r
|
|
|
6
6
|
- **Diagram Type:** {{ diagramType }}
|
|
7
7
|
- **Visual Style:** {{ diagramStyle }}
|
|
8
8
|
- **Aspect Ratio:** {{ aspectRatio }}
|
|
9
|
-
- **Language:**
|
|
9
|
+
- **Language:** English
|
|
10
10
|
|
|
11
11
|
# Your responsibilities:
|
|
12
12
|
1. Read and analyze the document content.
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { DIAGRAM_PLACEHOLDER, d2CodeBlockRegex } from "./d2-utils.mjs";
|
|
2
|
-
|
|
3
|
-
const diagramImageRegex = /<!--\s*DIAGRAM_IMAGE_START:[^>]+-->/g;
|
|
1
|
+
import { DIAGRAM_PLACEHOLDER, d2CodeBlockRegex, diagramImageStartRegex } from "./d2-utils.mjs";
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* Check if document content contains diagram-related content
|
|
@@ -24,7 +22,7 @@ export function hasDiagramContent(content) {
|
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
// Check for existing diagram images (DIAGRAM_IMAGE_START markers)
|
|
27
|
-
const imageMatches = Array.from(content.matchAll(
|
|
25
|
+
const imageMatches = Array.from(content.matchAll(diagramImageStartRegex));
|
|
28
26
|
if (imageMatches.length > 0) {
|
|
29
27
|
return true;
|
|
30
28
|
}
|
|
@@ -51,7 +49,7 @@ export function getDiagramTypeLabels(content) {
|
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
// Check for existing diagram images (AI-generated images)
|
|
54
|
-
const imageMatches = Array.from(content.matchAll(
|
|
52
|
+
const imageMatches = Array.from(content.matchAll(diagramImageStartRegex));
|
|
55
53
|
if (imageMatches.length > 0) {
|
|
56
54
|
labels.push("🍌 Image");
|
|
57
55
|
}
|
|
@@ -88,7 +86,7 @@ export function hasBananaImages(content) {
|
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
// Check for existing diagram images (DIAGRAM_IMAGE_START markers)
|
|
91
|
-
const imageMatches = Array.from(content.matchAll(
|
|
89
|
+
const imageMatches = Array.from(content.matchAll(diagramImageStartRegex));
|
|
92
90
|
if (imageMatches.length > 0) {
|
|
93
91
|
return true;
|
|
94
92
|
}
|
package/utils/d2-utils.mjs
CHANGED
|
@@ -10,6 +10,18 @@ export const d2CodeBlockRegex = /```d2.*\n([\s\S]*?)```/g;
|
|
|
10
10
|
|
|
11
11
|
export const DIAGRAM_PLACEHOLDER = "DIAGRAM_PLACEHOLDER";
|
|
12
12
|
|
|
13
|
+
// Diagram image regex patterns for reuse across the codebase
|
|
14
|
+
// Pattern 1: Match only the start marker (for checking existence)
|
|
15
|
+
export const diagramImageStartRegex = /<!--\s*DIAGRAM_IMAGE_START:[^>]+-->/g;
|
|
16
|
+
|
|
17
|
+
// Pattern 2: Match full diagram image block without capturing image path (for finding/replacing)
|
|
18
|
+
export const diagramImageBlockRegex =
|
|
19
|
+
/<!--\s*DIAGRAM_IMAGE_START:[^>]+-->\s*[\s\S]*?<!--\s*DIAGRAM_IMAGE_END\s*-->/g;
|
|
20
|
+
|
|
21
|
+
// Pattern 3: Match full diagram image block with image path capture (for extracting paths)
|
|
22
|
+
export const diagramImageWithPathRegex =
|
|
23
|
+
/<!--\s*DIAGRAM_IMAGE_START:[^>]+-->\s*!\[[^\]]*\]\(([^)]+)\)\s*<!--\s*DIAGRAM_IMAGE_END\s*-->/g;
|
|
24
|
+
|
|
13
25
|
export async function ensureTmpDir() {
|
|
14
26
|
const tmpDir = path.join(DOC_SMITH_DIR, TMP_DIR);
|
|
15
27
|
if (!(await fs.pathExists(path.join(tmpDir, ".gitignore")))) {
|
|
@@ -93,9 +105,8 @@ export function replaceDiagramsWithPlaceholder({ content, diagramIndex }) {
|
|
|
93
105
|
return content;
|
|
94
106
|
}
|
|
95
107
|
|
|
96
|
-
//
|
|
108
|
+
// Use exported regex to find all diagram locations
|
|
97
109
|
// We'll use a similar approach to findAllDiagramLocations
|
|
98
|
-
const diagramImageRegex = /<!-- DIAGRAM_IMAGE_START:[^>]+ -->[\s\S]*?<!-- DIAGRAM_IMAGE_END -->/g;
|
|
99
110
|
const mermaidCodeBlockRegex = /```mermaid.*\n([\s\S]*?)```/g;
|
|
100
111
|
|
|
101
112
|
// Find all diagram locations
|
|
@@ -113,36 +124,33 @@ export function replaceDiagramsWithPlaceholder({ content, diagramIndex }) {
|
|
|
113
124
|
}
|
|
114
125
|
|
|
115
126
|
// 2. Find DIAGRAM_IMAGE_START markers (generated images)
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
const imageMatches = Array.from(content.matchAll(diagramImageBlockRegex));
|
|
128
|
+
for (const match of imageMatches) {
|
|
118
129
|
locations.push({
|
|
119
130
|
type: "image",
|
|
120
131
|
start: match.index,
|
|
121
132
|
end: match.index + match[0].length,
|
|
122
133
|
});
|
|
123
|
-
match = diagramImageRegex.exec(content);
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
// 3. Find D2 code blocks
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
const d2Matches = Array.from(content.matchAll(d2CodeBlockRegex));
|
|
138
|
+
for (const match of d2Matches) {
|
|
129
139
|
locations.push({
|
|
130
140
|
type: "d2",
|
|
131
141
|
start: match.index,
|
|
132
142
|
end: match.index + match[0].length,
|
|
133
143
|
});
|
|
134
|
-
match = d2CodeBlockRegex.exec(content);
|
|
135
144
|
}
|
|
136
145
|
|
|
137
146
|
// 4. Find Mermaid code blocks
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
const mermaidMatches = Array.from(content.matchAll(mermaidCodeBlockRegex));
|
|
148
|
+
for (const match of mermaidMatches) {
|
|
140
149
|
locations.push({
|
|
141
150
|
type: "mermaid",
|
|
142
151
|
start: match.index,
|
|
143
152
|
end: match.index + match[0].length,
|
|
144
153
|
});
|
|
145
|
-
match = mermaidCodeBlockRegex.exec(content);
|
|
146
154
|
}
|
|
147
155
|
|
|
148
156
|
// Sort by position (top to bottom)
|
|
@@ -19,12 +19,10 @@ export async function extractDiagramImagePaths(content, path, docsDir) {
|
|
|
19
19
|
const imagePaths = [];
|
|
20
20
|
|
|
21
21
|
// Pattern to match: <!-- DIAGRAM_IMAGE_START:... --><!-- DIAGRAM_IMAGE_END -->
|
|
22
|
-
const
|
|
23
|
-
|
|
22
|
+
const { diagramImageWithPathRegex } = await import("./d2-utils.mjs");
|
|
23
|
+
const matches = Array.from(content.matchAll(diagramImageWithPathRegex));
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
let match = diagramPattern.exec(content);
|
|
27
|
-
while (match !== null) {
|
|
25
|
+
for (const match of matches) {
|
|
28
26
|
const imagePath = match[1];
|
|
29
27
|
|
|
30
28
|
// Resolve absolute path
|
|
@@ -49,8 +47,6 @@ export async function extractDiagramImagePaths(content, path, docsDir) {
|
|
|
49
47
|
if (await fs.pathExists(normalizedPath)) {
|
|
50
48
|
imagePaths.push(normalizedPath);
|
|
51
49
|
}
|
|
52
|
-
|
|
53
|
-
match = diagramPattern.exec(content);
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
return imagePaths;
|
|
@@ -3,9 +3,7 @@ import { readFileContent } from "./docs-finder-utils.mjs";
|
|
|
3
3
|
import { debug } from "./debug.mjs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import fs from "fs-extra";
|
|
6
|
-
import { d2CodeBlockRegex } from "./d2-utils.mjs";
|
|
7
|
-
const diagramImageRegex =
|
|
8
|
-
/<!--\s*DIAGRAM_IMAGE_START:[^>]+-->\s*!\[[^\]]*\]\(([^)]+)\)\s*<!--\s*DIAGRAM_IMAGE_END\s*-->/g;
|
|
6
|
+
import { d2CodeBlockRegex, diagramImageWithPathRegex } from "./d2-utils.mjs";
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* Find all translation files for a document
|
|
@@ -21,15 +19,24 @@ async function findTranslationFiles(docPath, docsDir, locale) {
|
|
|
21
19
|
try {
|
|
22
20
|
const files = readdirSync(docsDir);
|
|
23
21
|
const translationFiles = [];
|
|
22
|
+
const mainFileName = locale === "en" ? `${flatName}.md` : `${flatName}.${locale}.md`;
|
|
24
23
|
|
|
25
24
|
// Filter files to find translation files matching the pattern
|
|
26
25
|
for (const file of files) {
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
if (!file.endsWith(".md")) continue;
|
|
27
|
+
if (file === mainFileName) continue; // Skip main language file
|
|
28
|
+
|
|
29
|
+
// Case 1: File without language suffix (xxx.md) - this is English translation when main language is not English
|
|
30
|
+
if (file === `${flatName}.md` && locale !== "en") {
|
|
31
|
+
translationFiles.push({
|
|
32
|
+
language: "en",
|
|
33
|
+
fileName: file,
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Case 2: File with language suffix (xxx.{lang}.md) - all other translations
|
|
39
|
+
if (file.startsWith(`${flatName}.`) && file.match(/\.\w+(-\w+)?\.md$/)) {
|
|
33
40
|
const langMatch = file.match(/\.(\w+(-\w+)?)\.md$/);
|
|
34
41
|
if (langMatch && langMatch[1] !== locale) {
|
|
35
42
|
translationFiles.push({
|
|
@@ -54,18 +61,14 @@ async function findTranslationFiles(docPath, docsDir, locale) {
|
|
|
54
61
|
*/
|
|
55
62
|
function extractDiagramImagePaths(content) {
|
|
56
63
|
const images = [];
|
|
64
|
+
const matches = Array.from(content.matchAll(diagramImageWithPathRegex));
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
diagramImageRegex.lastIndex = 0;
|
|
60
|
-
|
|
61
|
-
let match = diagramImageRegex.exec(content);
|
|
62
|
-
while (match !== null) {
|
|
66
|
+
for (const match of matches) {
|
|
63
67
|
images.push({
|
|
64
68
|
path: match[1],
|
|
65
69
|
fullMatch: match[0],
|
|
66
70
|
index: match.index,
|
|
67
71
|
});
|
|
68
|
-
match = diagramImageRegex.exec(content);
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
return images;
|
|
@@ -118,7 +121,8 @@ export async function syncDiagramToTranslations(
|
|
|
118
121
|
const translationFilePath = path.join(docsDir, fileName);
|
|
119
122
|
const translationContent = await readFileContent(docsDir, fileName);
|
|
120
123
|
|
|
121
|
-
|
|
124
|
+
// Check for null or undefined (file read failure), but allow empty string (valid content)
|
|
125
|
+
if (translationContent === null || translationContent === undefined) {
|
|
122
126
|
debug(`⚠️ Could not read translation file: ${fileName}`);
|
|
123
127
|
result.skipped++;
|
|
124
128
|
continue;
|