@aigne/doc-smith 0.8.15-beta.5 โ 0.8.15-beta.7
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/clear/choose-contents.mjs +4 -4
- package/agents/clear/clear-auth-tokens.mjs +8 -8
- package/agents/clear/clear-deployment-config.mjs +2 -2
- package/agents/clear/clear-document-config.mjs +3 -3
- package/agents/clear/clear-document-structure.mjs +10 -10
- package/agents/clear/clear-generated-docs.mjs +103 -14
- package/agents/clear/clear-media-description.mjs +7 -7
- package/agents/media/load-media-description.mjs +12 -24
- package/agents/translate/index.yaml +1 -1
- package/agents/translate/record-translation-history.mjs +6 -2
- package/agents/update/save-and-translate-document.mjs +11 -0
- package/agents/utils/choose-docs.mjs +2 -1
- package/agents/utils/load-sources.mjs +2 -25
- package/agents/utils/save-doc.mjs +0 -9
- package/package.json +1 -1
- package/prompts/common/document/media-file-list-usage-rules.md +12 -0
- package/prompts/detail/custom/custom-components.md +38 -3
- package/prompts/detail/generate/system-prompt.md +2 -0
- package/prompts/detail/generate/user-prompt.md +3 -3
- package/prompts/detail/update/system-prompt.md +2 -0
- package/prompts/detail/update/user-prompt.md +2 -3
- package/prompts/translate/code-block.md +13 -3
- package/utils/docs-finder-utils.mjs +48 -0
- package/utils/file-utils.mjs +24 -3
- package/utils/history-utils.mjs +20 -8
- package/utils/markdown-checker.mjs +35 -1
- package/prompts/common/document/media-handling-rules.md +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.15-beta.7](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.15-beta.6...v0.8.15-beta.7) (2025-10-31)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add web-smith powered web-pages ([#229](https://github.com/AIGNE-io/aigne-doc-smith/issues/229)) ([c3c00c1](https://github.com/AIGNE-io/aigne-doc-smith/commit/c3c00c12f092b125b6adb1a13ed5ff9720fbdab7))
|
|
9
|
+
* support cleaning specific documents ([#231](https://github.com/AIGNE-io/aigne-doc-smith/issues/231)) ([67607c9](https://github.com/AIGNE-io/aigne-doc-smith/commit/67607c9ff3852cc81a29e5a11b2151d26879b000))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* tune custom component prompt and batch update history ([#228](https://github.com/AIGNE-io/aigne-doc-smith/issues/228)) ([ab13b97](https://github.com/AIGNE-io/aigne-doc-smith/commit/ab13b9737e5f111d0939f9c39ee76e13c0692a68))
|
|
15
|
+
|
|
16
|
+
## [0.8.15-beta.6](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.15-beta.5...v0.8.15-beta.6) (2025-10-30)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* ensure document embed image is accessible ([#226](https://github.com/AIGNE-io/aigne-doc-smith/issues/226)) ([47dfc5d](https://github.com/AIGNE-io/aigne-doc-smith/commit/47dfc5d48440f435258c7d4b5629712c7eb886e7))
|
|
22
|
+
|
|
3
23
|
## [0.8.15-beta.5](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.15-beta.4...v0.8.15-beta.5) (2025-10-29)
|
|
4
24
|
|
|
5
25
|
|
|
@@ -10,7 +10,7 @@ const TARGET_METADATA = {
|
|
|
10
10
|
generatedDocs: {
|
|
11
11
|
label: "Generated Documents",
|
|
12
12
|
description: ({ docsDir }) =>
|
|
13
|
-
`
|
|
13
|
+
`Select and delete specific generated documents in './${toDisplayPath(docsDir)}'. The documentation structure will be preserved.`,
|
|
14
14
|
agent: "clearGeneratedDocs",
|
|
15
15
|
},
|
|
16
16
|
documentStructure: {
|
|
@@ -150,9 +150,9 @@ export default async function chooseContents(input = {}, options = {}) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
const header = hasError
|
|
153
|
-
? "Cleanup finished with some issues
|
|
154
|
-
: "Cleanup completed successfully
|
|
155
|
-
const detailLines = results.map((item) =>
|
|
153
|
+
? "๐งน Cleanup finished with some issues.\n"
|
|
154
|
+
: "๐งน Cleanup completed successfully!\n";
|
|
155
|
+
const detailLines = results.map((item) => `${item.message}`).join("\n\n");
|
|
156
156
|
|
|
157
157
|
const suggestions = [];
|
|
158
158
|
results.forEach((result) => {
|
|
@@ -9,7 +9,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
9
9
|
// Check if the file exists
|
|
10
10
|
if (!existsSync(DOC_SMITH_ENV_FILE)) {
|
|
11
11
|
return {
|
|
12
|
-
message: "No site authorizations found to clear",
|
|
12
|
+
message: "๐ No site authorizations found to clear",
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -23,7 +23,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
23
23
|
|
|
24
24
|
if (siteHostnames.length === 0) {
|
|
25
25
|
return {
|
|
26
|
-
message: "No site authorizations found to clear",
|
|
26
|
+
message: "๐ No site authorizations found to clear",
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -58,7 +58,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
58
58
|
|
|
59
59
|
if (selectedSites.length === 0) {
|
|
60
60
|
return {
|
|
61
|
-
message: "No sites selected for clearing authorization",
|
|
61
|
+
message: "๐ No sites selected for clearing authorization",
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -68,7 +68,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
68
68
|
if (selectedSites.includes("__ALL__")) {
|
|
69
69
|
// Clear all site authorizations
|
|
70
70
|
await writeFile(DOC_SMITH_ENV_FILE, stringify({}));
|
|
71
|
-
results.push(
|
|
71
|
+
results.push(`โ Cleared site authorization for all sites (${siteHostnames.length} sites)`);
|
|
72
72
|
clearedCount = siteHostnames.length;
|
|
73
73
|
} else {
|
|
74
74
|
// Clear site authorizations for selected sites
|
|
@@ -79,7 +79,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
79
79
|
// Remove the entire site object
|
|
80
80
|
delete updatedEnvs[hostname];
|
|
81
81
|
|
|
82
|
-
results.push(
|
|
82
|
+
results.push(`โ Cleared site authorization for ${chalk.cyan(hostname)}`);
|
|
83
83
|
clearedCount++;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -87,8 +87,8 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
87
87
|
await writeFile(DOC_SMITH_ENV_FILE, stringify(updatedEnvs));
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const header =
|
|
91
|
-
const detailLines = results.join("\n");
|
|
90
|
+
const header = `๐ Successfully cleared site authorizations!`;
|
|
91
|
+
const detailLines = results.map((item) => ` ${item}`).join("\n");
|
|
92
92
|
|
|
93
93
|
const message = [header, "", detailLines, ""].filter(Boolean).join("\n");
|
|
94
94
|
|
|
@@ -99,7 +99,7 @@ export default async function clearAuthTokens(_input = {}, options = {}) {
|
|
|
99
99
|
};
|
|
100
100
|
} catch (error) {
|
|
101
101
|
return {
|
|
102
|
-
message:
|
|
102
|
+
message: `โ ๏ธ Failed to clear site authorizations: ${error.message}`,
|
|
103
103
|
error: true,
|
|
104
104
|
};
|
|
105
105
|
}
|
|
@@ -36,12 +36,12 @@ export default async function clearDeploymentConfig(input = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
|
-
message:
|
|
39
|
+
message: `๐ฆ Cleared appUrl from config file (${displayPath})`,
|
|
40
40
|
};
|
|
41
41
|
} catch (error) {
|
|
42
42
|
return {
|
|
43
43
|
error: true,
|
|
44
|
-
message:
|
|
44
|
+
message: `โ ๏ธ Failed to clear deployment config: ${error.message}`,
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -10,8 +10,8 @@ export default async function clearDocumentConfig({ workDir }) {
|
|
|
10
10
|
await rm(documentConfigPath, { recursive: true, force: true });
|
|
11
11
|
|
|
12
12
|
const message = existed
|
|
13
|
-
?
|
|
14
|
-
:
|
|
13
|
+
? `โ๏ธ Cleared document configuration (${displayPath})`
|
|
14
|
+
: `โ๏ธ Document configuration already empty (${displayPath})`;
|
|
15
15
|
|
|
16
16
|
const suggestions = existed
|
|
17
17
|
? ["Run `aigne doc init` to generate a fresh configuration file."]
|
|
@@ -25,7 +25,7 @@ export default async function clearDocumentConfig({ workDir }) {
|
|
|
25
25
|
};
|
|
26
26
|
} catch (error) {
|
|
27
27
|
return {
|
|
28
|
-
message:
|
|
28
|
+
message: `โ ๏ธ Failed to clear document configuration: ${error.message}`,
|
|
29
29
|
error: true,
|
|
30
30
|
path: displayPath,
|
|
31
31
|
};
|
|
@@ -16,8 +16,8 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
16
16
|
|
|
17
17
|
const structureDisplayPath = toDisplayPath(structurePlanPath);
|
|
18
18
|
const structureMessage = structureExists
|
|
19
|
-
?
|
|
20
|
-
:
|
|
19
|
+
? `โ Cleared documentation structure (${structureDisplayPath})`
|
|
20
|
+
: `โข Documentation structure already empty (${structureDisplayPath})`;
|
|
21
21
|
|
|
22
22
|
results.push({
|
|
23
23
|
type: "structure",
|
|
@@ -29,7 +29,7 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
29
29
|
results.push({
|
|
30
30
|
type: "structure",
|
|
31
31
|
error: true,
|
|
32
|
-
message:
|
|
32
|
+
message: `โ Failed to clear documentation structure: ${error.message}`,
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -41,8 +41,8 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
41
41
|
|
|
42
42
|
const docsDisplayPath = toDisplayPath(docsDir);
|
|
43
43
|
const docsMessage = docsExists
|
|
44
|
-
?
|
|
45
|
-
:
|
|
44
|
+
? `โ Cleared documents directory (${docsDisplayPath})`
|
|
45
|
+
: `โข Documents directory already empty (${docsDisplayPath})`;
|
|
46
46
|
|
|
47
47
|
results.push({
|
|
48
48
|
type: "documents",
|
|
@@ -54,7 +54,7 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
54
54
|
results.push({
|
|
55
55
|
type: "documents",
|
|
56
56
|
error: true,
|
|
57
|
-
message:
|
|
57
|
+
message: `โ Failed to clear documents directory: ${error.message}`,
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -65,14 +65,14 @@ export default async function clearDocumentStructure(input = {}, _options = {})
|
|
|
65
65
|
|
|
66
66
|
let header;
|
|
67
67
|
if (errorItems > 0) {
|
|
68
|
-
header = "Documentation Structure cleanup finished with some issues.";
|
|
68
|
+
header = "โ ๏ธ Documentation Structure cleanup finished with some issues.";
|
|
69
69
|
} else if (clearedItems > 0) {
|
|
70
|
-
header = "Documentation Structure cleared successfully!";
|
|
70
|
+
header = "๐ Documentation Structure cleared successfully!";
|
|
71
71
|
} else {
|
|
72
|
-
header = "Documentation Structure already empty.";
|
|
72
|
+
header = "๐ Documentation Structure already empty.";
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const detailLines = results.map((item) =>
|
|
75
|
+
const detailLines = results.map((item) => ` ${item.message}`).join("\n");
|
|
76
76
|
const message = [header, "", detailLines].filter(Boolean).join("\n");
|
|
77
77
|
|
|
78
78
|
return {
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import { pathExists, resolveToAbsolute, toDisplayPath } from "../../utils/file-utils.mjs";
|
|
4
|
+
import {
|
|
5
|
+
pathToFlatName,
|
|
6
|
+
generateFileName,
|
|
7
|
+
loadDocumentStructure,
|
|
8
|
+
} from "../../utils/docs-finder-utils.mjs";
|
|
9
|
+
import chooseDocs from "../utils/choose-docs.mjs";
|
|
3
10
|
|
|
4
|
-
export default async function clearGeneratedDocs(input = {},
|
|
5
|
-
const { docsDir } = input;
|
|
11
|
+
export default async function clearGeneratedDocs(input = {}, options = {}) {
|
|
12
|
+
const { docsDir, outputDir, locale, translateLanguages } = input;
|
|
6
13
|
|
|
7
14
|
if (!docsDir) {
|
|
8
15
|
return {
|
|
9
|
-
message: "No generated documents directory specified",
|
|
16
|
+
message: "๐ No generated documents directory specified",
|
|
10
17
|
};
|
|
11
18
|
}
|
|
12
19
|
|
|
@@ -14,23 +21,104 @@ export default async function clearGeneratedDocs(input = {}, _options = {}) {
|
|
|
14
21
|
const displayPath = toDisplayPath(generatedDocsPath);
|
|
15
22
|
|
|
16
23
|
try {
|
|
17
|
-
const
|
|
18
|
-
|
|
24
|
+
const dirExists = await pathExists(generatedDocsPath);
|
|
25
|
+
if (!dirExists) {
|
|
26
|
+
return {
|
|
27
|
+
message: `๐ Generated documents directory does not exist (${displayPath})`,
|
|
28
|
+
cleared: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
19
31
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
const documentExecutionStructure = (await loadDocumentStructure(outputDir)) || [];
|
|
33
|
+
// select documents interactively
|
|
34
|
+
const chooseResult = await chooseDocs(
|
|
35
|
+
{
|
|
36
|
+
docs: [], // Empty to trigger interactive selection
|
|
37
|
+
documentExecutionStructure,
|
|
38
|
+
docsDir: generatedDocsPath,
|
|
39
|
+
locale: locale || "en",
|
|
40
|
+
isTranslate: false,
|
|
41
|
+
title: "Select documents to delete:",
|
|
42
|
+
feedback: "Skip feedback",
|
|
43
|
+
requiredFeedback: false,
|
|
44
|
+
},
|
|
45
|
+
options,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (!chooseResult?.selectedDocs || chooseResult.selectedDocs.length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
message: "๐ No documents selected for deletion",
|
|
51
|
+
cleared: false,
|
|
52
|
+
path: displayPath,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extract file names
|
|
57
|
+
const filesToDelete = new Set();
|
|
58
|
+
const allLanguages = [locale || "en", ...(translateLanguages || [])];
|
|
59
|
+
|
|
60
|
+
for (const selectedDoc of chooseResult.selectedDocs) {
|
|
61
|
+
// Convert path to flat filename format using utility function
|
|
62
|
+
const flatName = pathToFlatName(selectedDoc.path);
|
|
63
|
+
|
|
64
|
+
// Generate file names for all languages
|
|
65
|
+
for (const lang of allLanguages) {
|
|
66
|
+
const fileName = generateFileName(flatName, lang);
|
|
67
|
+
filesToDelete.add(fileName);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (filesToDelete.size === 0) {
|
|
72
|
+
return {
|
|
73
|
+
message: "๐ No documents were deleted.",
|
|
74
|
+
cleared: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Delete selected files (including all language versions)
|
|
79
|
+
const deletedFiles = [];
|
|
80
|
+
const failedFiles = [];
|
|
81
|
+
let hasError = false;
|
|
82
|
+
|
|
83
|
+
for (const file of filesToDelete) {
|
|
84
|
+
try {
|
|
85
|
+
const filePath = join(generatedDocsPath, file);
|
|
86
|
+
await rm(filePath, { force: true });
|
|
87
|
+
deletedFiles.push(file);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
hasError = true;
|
|
90
|
+
failedFiles.push({ file, error: error.message });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Build result message
|
|
95
|
+
const deletedCount = deletedFiles.length;
|
|
96
|
+
const failedCount = failedFiles.length;
|
|
97
|
+
|
|
98
|
+
let message = "";
|
|
99
|
+
if (deletedCount > 0) {
|
|
100
|
+
const lastIndex = deletedFiles.length - 1;
|
|
101
|
+
message = `๐ Deleted ${deletedCount} document(s) in "${displayPath}":\n${deletedFiles
|
|
102
|
+
.map((f, i) => ` ${i === lastIndex ? "โโ" : "โโ"} ${f}`)
|
|
103
|
+
.join("\n")}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (failedCount > 0) {
|
|
107
|
+
const lastIndex = failedFiles.length - 1;
|
|
108
|
+
message = `โ ๏ธ Failed to delete ${failedCount} document(s) in "${displayPath}":\n${failedFiles
|
|
109
|
+
.map((f, i) => ` ${i === lastIndex ? "โโ" : "โโ"} ${f.file}: ${f.error}`)
|
|
110
|
+
.join("\n")}`;
|
|
111
|
+
}
|
|
23
112
|
|
|
24
113
|
return {
|
|
25
114
|
message,
|
|
26
|
-
cleared:
|
|
27
|
-
|
|
115
|
+
cleared: deletedCount > 0,
|
|
116
|
+
error: hasError,
|
|
28
117
|
};
|
|
29
118
|
} catch (error) {
|
|
30
119
|
return {
|
|
31
|
-
message:
|
|
120
|
+
message: `โ ๏ธ Failed to clear generated documents: ${error.message}`,
|
|
32
121
|
error: true,
|
|
33
|
-
path: displayPath,
|
|
34
122
|
};
|
|
35
123
|
}
|
|
36
124
|
}
|
|
@@ -46,5 +134,6 @@ clearGeneratedDocs.input_schema = {
|
|
|
46
134
|
required: ["docsDir"],
|
|
47
135
|
};
|
|
48
136
|
|
|
49
|
-
clearGeneratedDocs.taskTitle = "Clear
|
|
50
|
-
clearGeneratedDocs.description =
|
|
137
|
+
clearGeneratedDocs.taskTitle = "Clear generated documents";
|
|
138
|
+
clearGeneratedDocs.description =
|
|
139
|
+
"Select and delete specific generated documents from the docs directory";
|
|
@@ -11,7 +11,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
11
11
|
// Check if the cache file exists
|
|
12
12
|
if (!existsSync(cacheFilePath)) {
|
|
13
13
|
return {
|
|
14
|
-
message: "No media descriptions found to clear",
|
|
14
|
+
message: "๐ผ๏ธ No media descriptions found to clear",
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -26,7 +26,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
26
26
|
|
|
27
27
|
if (mediaHashes.length === 0) {
|
|
28
28
|
return {
|
|
29
|
-
message: "No media descriptions found to clear",
|
|
29
|
+
message: "๐ผ๏ธ No media descriptions found to clear",
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -68,7 +68,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
68
68
|
|
|
69
69
|
if (selectedHashes.length === 0) {
|
|
70
70
|
return {
|
|
71
|
-
message: "No media files selected for clearing descriptions",
|
|
71
|
+
message: "๐ผ๏ธ No media files selected for clearing descriptions",
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -84,7 +84,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
84
84
|
lastUpdated: new Date().toISOString(),
|
|
85
85
|
}),
|
|
86
86
|
);
|
|
87
|
-
results.push(
|
|
87
|
+
results.push(`โ Cleared descriptions for all media files (${mediaHashes.length} files)`);
|
|
88
88
|
clearedCount = mediaHashes.length;
|
|
89
89
|
} else {
|
|
90
90
|
// Clear descriptions for selected files
|
|
@@ -94,7 +94,7 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
94
94
|
if (updatedCache[hash]) {
|
|
95
95
|
const filename = path.basename(updatedCache[hash].path);
|
|
96
96
|
delete updatedCache[hash];
|
|
97
|
-
results.push(
|
|
97
|
+
results.push(`โ Cleared description for ${chalk.cyan(filename)}`);
|
|
98
98
|
clearedCount++;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
@@ -108,8 +108,8 @@ export default async function clearMediaDescription(_input = {}, options = {}) {
|
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const header =
|
|
112
|
-
const detailLines = results.join("\n");
|
|
111
|
+
const header = `๐ผ๏ธ Successfully cleared media descriptions`;
|
|
112
|
+
const detailLines = results.map((item) => ` ${item}`).join("\n");
|
|
113
113
|
|
|
114
114
|
const message = [header, "", detailLines, ""].filter(Boolean).join("\n");
|
|
115
115
|
|
|
@@ -163,31 +163,19 @@ export default async function loadMediaDescription(input, options) {
|
|
|
163
163
|
let enhancedAssetsContent = "# Available Media Assets for Documentation\n\n";
|
|
164
164
|
|
|
165
165
|
if (mediaFiles.length > 0) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (asset.type === "image" || asset.type === "video") {
|
|
176
|
-
const mediaHash = mediaHashMap.get(asset.path);
|
|
177
|
-
const cachedDesc = cache[mediaHash];
|
|
178
|
-
if (cachedDesc?.description) {
|
|
179
|
-
enhancedAssetsContent += ` description: "${cachedDesc.description}"\n`;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Add dimensions for images and videos
|
|
184
|
-
if (asset.width && asset.height) {
|
|
185
|
-
enhancedAssetsContent += ` width: ${asset.width}\n`;
|
|
186
|
-
enhancedAssetsContent += ` height: ${asset.height}\n`;
|
|
166
|
+
const assets = mediaFiles.map((x) => {
|
|
167
|
+
const mediaHash = mediaHashMap.get(x.path);
|
|
168
|
+
const description = cache[mediaHash]?.description;
|
|
169
|
+
const result = {
|
|
170
|
+
name: x.name,
|
|
171
|
+
path: x.path,
|
|
172
|
+
};
|
|
173
|
+
if (description) {
|
|
174
|
+
result.description = description;
|
|
187
175
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
enhancedAssetsContent +=
|
|
176
|
+
return result;
|
|
177
|
+
});
|
|
178
|
+
enhancedAssetsContent += stringify(assets);
|
|
191
179
|
}
|
|
192
180
|
|
|
193
181
|
return {
|
|
@@ -33,12 +33,12 @@ skills:
|
|
|
33
33
|
name: batchTranslateDocument
|
|
34
34
|
skills:
|
|
35
35
|
- ../translate/translate-multilingual.yaml
|
|
36
|
-
- url: ./record-translation-history.mjs
|
|
37
36
|
iterate_on: selectedDocs
|
|
38
37
|
concurrency: 10
|
|
39
38
|
- url: ../utils/check-feedback-refiner.mjs
|
|
40
39
|
default_input:
|
|
41
40
|
stage: translation_refine
|
|
41
|
+
- url: ./record-translation-history.mjs
|
|
42
42
|
- url: ../utils/action-success.mjs
|
|
43
43
|
default_input:
|
|
44
44
|
action: 'โ
Translation completed'
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { recordUpdate } from "../../utils/history-utils.mjs";
|
|
2
2
|
|
|
3
|
-
export default function recordTranslationHistory({
|
|
3
|
+
export default function recordTranslationHistory({ selectedPaths, feedback }) {
|
|
4
4
|
// Skip if no feedback provided
|
|
5
5
|
if (!feedback?.trim()) {
|
|
6
6
|
return {};
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
if (!Array.isArray(selectedPaths) || selectedPaths.length === 0) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
// Record translation history for this document
|
|
10
14
|
recordUpdate({
|
|
11
15
|
operation: "translation_update",
|
|
12
16
|
feedback: feedback.trim(),
|
|
13
|
-
|
|
17
|
+
docPaths: selectedPaths,
|
|
14
18
|
});
|
|
15
19
|
|
|
16
20
|
return {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pMap from "p-map";
|
|
2
|
+
import { recordUpdate } from "../../utils/history-utils.mjs";
|
|
2
3
|
|
|
3
4
|
export default async function saveAndTranslateDocument(input, options) {
|
|
4
5
|
const { selectedDocs, docsDir, translateLanguages, locale } = input;
|
|
@@ -7,6 +8,16 @@ export default async function saveAndTranslateDocument(input, options) {
|
|
|
7
8
|
return {};
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
// Record history if feedback is provided
|
|
12
|
+
const doc = selectedDocs[0];
|
|
13
|
+
if (doc.feedback?.trim()) {
|
|
14
|
+
recordUpdate({
|
|
15
|
+
operation: "document_update",
|
|
16
|
+
feedback: doc.feedback.trim(),
|
|
17
|
+
docPaths: selectedDocs.map((v) => v.path),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
// Only prompt user if translation is actually needed
|
|
11
22
|
let shouldTranslate = false;
|
|
12
23
|
if (
|
|
@@ -18,6 +18,7 @@ export default async function chooseDocs(
|
|
|
18
18
|
locale,
|
|
19
19
|
reset = false,
|
|
20
20
|
requiredFeedback = true,
|
|
21
|
+
title,
|
|
21
22
|
},
|
|
22
23
|
options,
|
|
23
24
|
) {
|
|
@@ -65,7 +66,7 @@ export default async function chooseDocs(
|
|
|
65
66
|
|
|
66
67
|
// Let user select multiple files
|
|
67
68
|
selectedFiles = await options.prompts.checkbox({
|
|
68
|
-
message: getActionText(isTranslate, "Select documents to {action}:"),
|
|
69
|
+
message: title || getActionText(isTranslate, "Select documents to {action}:"),
|
|
69
70
|
source: (term) => {
|
|
70
71
|
if (!term) return choices;
|
|
71
72
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
DEFAULT_INCLUDE_PATTERNS,
|
|
22
22
|
} from "../../utils/constants/index.mjs";
|
|
23
23
|
import { isOpenAPISpecFile } from "../../utils/openapi/index.mjs";
|
|
24
|
+
import { loadDocumentStructure } from "../../utils/docs-finder-utils.mjs";
|
|
24
25
|
|
|
25
26
|
export default async function loadSources(
|
|
26
27
|
{
|
|
@@ -231,31 +232,7 @@ export default async function loadSources(
|
|
|
231
232
|
const allFilesPaths = sourceFiles.map((x) => `- ${toRelativePath(x.sourceId)}`).join("\n");
|
|
232
233
|
|
|
233
234
|
// Get the last documentation structure
|
|
234
|
-
|
|
235
|
-
if (outputDir) {
|
|
236
|
-
const documentStructurePath = path.join(outputDir, "structure-plan.json");
|
|
237
|
-
try {
|
|
238
|
-
const documentExecutionStructure = await readFile(documentStructurePath, "utf8");
|
|
239
|
-
if (documentExecutionStructure?.trim()) {
|
|
240
|
-
try {
|
|
241
|
-
// Validate that the content looks like JSON before parsing
|
|
242
|
-
const trimmedContent = documentExecutionStructure.trim();
|
|
243
|
-
if (trimmedContent.startsWith("{") || trimmedContent.startsWith("[")) {
|
|
244
|
-
originalDocumentStructure = JSON.parse(documentExecutionStructure);
|
|
245
|
-
} else {
|
|
246
|
-
console.warn(`structure-plan.json contains non-JSON content, skipping parse`);
|
|
247
|
-
}
|
|
248
|
-
} catch (err) {
|
|
249
|
-
console.error(`Failed to parse structure-plan.json: ${err.message}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
} catch (err) {
|
|
253
|
-
if (err.code !== "ENOENT") {
|
|
254
|
-
console.warn(`Error reading structure-plan.json: ${err.message}`);
|
|
255
|
-
}
|
|
256
|
-
// The file does not exist or is not readable, originalDocumentStructure remains undefined
|
|
257
|
-
}
|
|
258
|
-
}
|
|
235
|
+
const originalDocumentStructure = await loadDocumentStructure(outputDir);
|
|
259
236
|
|
|
260
237
|
// Get the last output result of the specified path
|
|
261
238
|
let content;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { recordUpdate } from "../../utils/history-utils.mjs";
|
|
2
1
|
import { shutdownMermaidWorkerPool } from "../../utils/mermaid-worker-pool.mjs";
|
|
3
2
|
import { saveDoc as _saveDoc } from "../../utils/utils.mjs";
|
|
4
3
|
|
|
@@ -20,14 +19,6 @@ export default async function saveDoc({
|
|
|
20
19
|
locale,
|
|
21
20
|
});
|
|
22
21
|
|
|
23
|
-
if (feedback?.trim()) {
|
|
24
|
-
recordUpdate({
|
|
25
|
-
operation: "document_update",
|
|
26
|
-
feedback: feedback.trim(),
|
|
27
|
-
documentPath: path,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
22
|
if (isShowMessage) {
|
|
32
23
|
// Shutdown mermaid worker pool to ensure clean exit
|
|
33
24
|
try {
|
package/package.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<media_file_list_usage_rules>
|
|
2
|
+
|
|
3
|
+
**Usage Workflow**
|
|
4
|
+
1. Read the `<media_file_list>` data and take note of each file's `path` and `description` as references.
|
|
5
|
+
2. Combine those descriptions with the current document's content to decide which images should be used and where they should be inserted.
|
|
6
|
+
3. Confirm that every inserted image path comes from `<media_file_list>`. If a path is missing from that list, replace it with one that is included.
|
|
7
|
+
|
|
8
|
+
**Usage Requirements**
|
|
9
|
+
- Insert images with Markdown syntax: ``.
|
|
10
|
+
- Never invent, reinterpret, fabricate, normalize, or rewrite any media file path under any circumstances.
|
|
11
|
+
|
|
12
|
+
</media_file_list_usage_rules>
|
|
@@ -72,12 +72,11 @@ Suitable for displaying multiple links using a card list format, providing a ric
|
|
|
72
72
|
|
|
73
73
|
### Attributes
|
|
74
74
|
|
|
75
|
-
- data-columns (optional):
|
|
76
|
-
- Must contain multiple <x-card> elements internally.
|
|
75
|
+
- data-columns (optional): Must be an **integer โฅ 2**. Values below 2 are disallowed. Default is 2.
|
|
77
76
|
|
|
78
77
|
### Children
|
|
79
78
|
|
|
80
|
-
- Must contain multiple
|
|
79
|
+
- Must contain multiple `<x-card>` elements internally.
|
|
81
80
|
|
|
82
81
|
### Usage Rules
|
|
83
82
|
|
|
@@ -107,6 +106,25 @@ Example 2: Two-column cards with images
|
|
|
107
106
|
</x-cards>
|
|
108
107
|
```
|
|
109
108
|
|
|
109
|
+
### Bad Examples
|
|
110
|
+
|
|
111
|
+
Example 1: Using a single-column layout (`data-columns="1"`) is not allowed
|
|
112
|
+
|
|
113
|
+
```md
|
|
114
|
+
<x-cards data-columns="1">
|
|
115
|
+
<x-card data-title="Feature 1" data-icon="lucide:rocket">Description of Feature 1.</x-card>
|
|
116
|
+
<x-card data-title="Feature 2" data-icon="lucide:bolt">Description of Feature 2.</x-card>
|
|
117
|
+
</x-cards>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Example 2: Contains only one `<x-card>` (must include multiple cards)
|
|
121
|
+
|
|
122
|
+
```md
|
|
123
|
+
<x-cards data-columns="2">
|
|
124
|
+
<x-card data-title="Card A" data-image="https://picsum.photos/id/10/300/300">Content A</x-card>
|
|
125
|
+
</x-cards>
|
|
126
|
+
```
|
|
127
|
+
|
|
110
128
|
## XField: Structured data field
|
|
111
129
|
|
|
112
130
|
Suitable for displaying API parameters, return values, context data, and any structured data with metadata in a clean, organized format. Supports nested structures for complex data types.
|
|
@@ -414,6 +432,7 @@ Used to group multiple related `<x-field>` elements at the top level, indicating
|
|
|
414
432
|
|
|
415
433
|
- **Top-Level Only**: Used only at the top level for grouping related `<x-field>` elements. Cannot be nested inside other `<x-field>` or `<x-field-group>` elements
|
|
416
434
|
- **Structured Data Only**: Use `<x-field-group>` for fields **other than simple types** (`string`, `number`, `boolean`, `symbol`), e.g., Properties, Context, Parameters, Return values. For simple-type fields, use plain Markdown text.
|
|
435
|
+
- **Spacing Around**: Always insert a blank line before and after `<x-field-group>` when itโs adjacent to Markdown content.
|
|
417
436
|
|
|
418
437
|
### Good Examples
|
|
419
438
|
|
|
@@ -482,4 +501,20 @@ Example 5: Using x-field-group for simple-type (violates "Structured Data Only"
|
|
|
482
501
|
</x-field-group>
|
|
483
502
|
```
|
|
484
503
|
|
|
504
|
+
Example 6: Missing blank line before x-field-group (violates "Spacing Around" rule)
|
|
505
|
+
|
|
506
|
+
```md
|
|
507
|
+
**Parameters**
|
|
508
|
+
<x-field-group>
|
|
509
|
+
<x-field data-name="initialState" data-type="any" data-required="false">
|
|
510
|
+
<x-field-desc markdown>The initial state value.</x-field-desc>
|
|
511
|
+
</x-field>
|
|
512
|
+
</x-field-group>
|
|
513
|
+
|
|
514
|
+
`useReducer` returns an array with two items:
|
|
515
|
+
<x-field-group>
|
|
516
|
+
<x-field data-name="dispatch" data-type="function" data-desc="A function that you can call with an action to update the state."></x-field>
|
|
517
|
+
</x-field-group>
|
|
518
|
+
```
|
|
519
|
+
|
|
485
520
|
</custom_components_usage>
|
|
@@ -45,6 +45,8 @@ Custom code block generation rules:
|
|
|
45
45
|
|
|
46
46
|
{% include "../d2-diagram/guide.md" %}
|
|
47
47
|
|
|
48
|
+
{% include "../../common/document/media-file-list-usage-rules.md" %}
|
|
49
|
+
|
|
48
50
|
Tool result usage rules:
|
|
49
51
|
- Only use the `"role": "tool"` result as the datasource for document enhancement.
|
|
50
52
|
- Do not include `"role": "agent"` content in the final output.
|
|
@@ -38,6 +38,8 @@ Custom component optimization rules:
|
|
|
38
38
|
Custom code block optimization rules:
|
|
39
39
|
{% include "../custom/custom-code-block.md" %}
|
|
40
40
|
|
|
41
|
+
{% include "../../common/document/media-file-list-usage-rules.md" %}
|
|
42
|
+
|
|
41
43
|
Diagram generation rules:
|
|
42
44
|
{% include "../d2-diagram/guide.md" %}
|
|
43
45
|
<diagram_generation_rules>
|
|
@@ -2,13 +2,23 @@
|
|
|
2
2
|
The following formats are considered Code Blocks:
|
|
3
3
|
|
|
4
4
|
- Wrapped with ```
|
|
5
|
-
- Supports configurations: language, title, icon
|
|
5
|
+
- Supports configurations: language, optional title, optional icon (icon uses key=value)
|
|
6
|
+
- title is free text placed after the language (not as title=xxx), may contain spaces, and **must NEVER be wrapped in quotes**
|
|
6
7
|
- content can be code, command line examples, text or any other content
|
|
7
8
|
|
|
8
9
|
<code_block_sample>
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
- `language`: javascript
|
|
12
|
+
- `title`: Modern: Using createRoot()
|
|
13
|
+
- `icon`: logos:javascript
|
|
14
|
+
|
|
15
|
+
```javascript Modern: Using createRoot() icon=logos:javascript
|
|
16
|
+
import { createRoot } from 'react-dom/client'
|
|
17
|
+
|
|
18
|
+
const container = document.getElementById('root')
|
|
19
|
+
const root = createRoot(container)
|
|
20
|
+
|
|
21
|
+
root.unmount()
|
|
12
22
|
```
|
|
13
23
|
|
|
14
24
|
</code_block_sample>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { pathExists } from "./file-utils.mjs";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Get action-specific text based on isTranslate flag
|
|
@@ -276,3 +277,50 @@ export function addFeedbackToItems(items, feedback) {
|
|
|
276
277
|
feedback: feedback.trim(),
|
|
277
278
|
}));
|
|
278
279
|
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Load document execution structure from structure-plan.json
|
|
283
|
+
* @param {string} outputDir - Output directory containing structure-plan.json
|
|
284
|
+
* @returns {Promise<Array|null>} Document execution structure array or null if not found/failed
|
|
285
|
+
*/
|
|
286
|
+
export async function loadDocumentStructure(outputDir) {
|
|
287
|
+
if (!outputDir) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const structurePlanPath = join(outputDir, "structure-plan.json");
|
|
293
|
+
const structureExists = await pathExists(structurePlanPath);
|
|
294
|
+
|
|
295
|
+
if (!structureExists) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const structureContent = await readFile(structurePlanPath, "utf8");
|
|
300
|
+
if (!structureContent?.trim()) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
// Validate that the content looks like JSON before parsing
|
|
306
|
+
const trimmedContent = structureContent.trim();
|
|
307
|
+
if (!trimmedContent.startsWith("[") && !trimmedContent.startsWith("{")) {
|
|
308
|
+
console.warn("structure-plan.json contains non-JSON content, skipping parse");
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const parsed = JSON.parse(structureContent);
|
|
313
|
+
// Return array if it's an array, otherwise return null
|
|
314
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
315
|
+
} catch (parseError) {
|
|
316
|
+
console.error(`Failed to parse structure-plan.json: ${parseError.message}`);
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
} catch (readError) {
|
|
320
|
+
// Only warn if it's not a "file not found" error
|
|
321
|
+
if (readError.code !== "ENOENT") {
|
|
322
|
+
console.warn(`Error reading structure-plan.json: ${readError.message}`);
|
|
323
|
+
}
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
}
|
package/utils/file-utils.mjs
CHANGED
|
@@ -408,10 +408,31 @@ async function isTextFile(filePath) {
|
|
|
408
408
|
export function isRemoteFile(fileUrl) {
|
|
409
409
|
if (typeof fileUrl !== "string") return false;
|
|
410
410
|
|
|
411
|
-
|
|
412
|
-
|
|
411
|
+
try {
|
|
412
|
+
const url = new URL(fileUrl);
|
|
413
|
+
// Only accept http and https url
|
|
414
|
+
if (["http:", "https:"].includes(url.protocol)) {
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
// other protocol will be treated as bad url
|
|
418
|
+
return false;
|
|
419
|
+
} catch {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export async function isRemoteFileAvailable(fileUrl) {
|
|
425
|
+
if (!isRemoteFile(fileUrl)) return false;
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const res = await fetch(fileUrl, {
|
|
429
|
+
method: "HEAD",
|
|
430
|
+
});
|
|
431
|
+
return res.ok;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
debug(`Failed to check HTTP file availability: ${fileUrl} - ${error.message}`);
|
|
434
|
+
return false;
|
|
413
435
|
}
|
|
414
|
-
return false;
|
|
415
436
|
}
|
|
416
437
|
|
|
417
438
|
export async function isRemoteTextFile(fileUrl) {
|
package/utils/history-utils.mjs
CHANGED
|
@@ -99,7 +99,7 @@ function recordUpdateGit({ feedback }) {
|
|
|
99
99
|
/**
|
|
100
100
|
* Records an update in the YAML file.
|
|
101
101
|
*/
|
|
102
|
-
function recordUpdateYaml({ operation, feedback,
|
|
102
|
+
function recordUpdateYaml({ operation, feedback, docPaths = null }) {
|
|
103
103
|
try {
|
|
104
104
|
const docSmithDir = join(process.cwd(), DOC_SMITH_DIR);
|
|
105
105
|
if (!existsSync(docSmithDir)) {
|
|
@@ -125,9 +125,9 @@ function recordUpdateYaml({ operation, feedback, documentPath = null }) {
|
|
|
125
125
|
feedback,
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
-
// Add document
|
|
129
|
-
if (
|
|
130
|
-
entry.
|
|
128
|
+
// Add document paths if provided
|
|
129
|
+
if (Array.isArray(docPaths) && docPaths.length > 0) {
|
|
130
|
+
entry.docPaths = docPaths;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// Add to beginning (newest first)
|
|
@@ -153,14 +153,14 @@ function recordUpdateYaml({ operation, feedback, documentPath = null }) {
|
|
|
153
153
|
* @param {Object} params
|
|
154
154
|
* @param {string} params.operation - The type of operation (e.g., 'document_update', 'structure_update', 'translation_update').
|
|
155
155
|
* @param {string} params.feedback - The user's feedback text.
|
|
156
|
-
* @param {string} params.
|
|
156
|
+
* @param {string[]} [params.docPaths] - Document path list for updates.
|
|
157
157
|
*/
|
|
158
|
-
export function recordUpdate({ operation, feedback,
|
|
158
|
+
export function recordUpdate({ operation, feedback, docPaths = null }) {
|
|
159
159
|
// Skip if no feedback
|
|
160
160
|
if (!feedback?.trim()) return;
|
|
161
161
|
|
|
162
162
|
// Always record in YAML
|
|
163
|
-
recordUpdateYaml({ operation, feedback,
|
|
163
|
+
recordUpdateYaml({ operation, feedback, docPaths });
|
|
164
164
|
|
|
165
165
|
// Also record in git if git is available and not in a git repository
|
|
166
166
|
if (isGitAvailable() && !isInGitRepository(process.cwd())) {
|
|
@@ -183,7 +183,19 @@ export function getHistory() {
|
|
|
183
183
|
|
|
184
184
|
try {
|
|
185
185
|
const content = readFileSync(historyPath, "utf8");
|
|
186
|
-
|
|
186
|
+
const parsed = parse(content) || { entries: [] };
|
|
187
|
+
const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
188
|
+
|
|
189
|
+
const normalized = entries.map((entry) => {
|
|
190
|
+
if (!entry) return entry;
|
|
191
|
+
// Normalize legacy entries: documentPath -> docPaths: [documentPath]
|
|
192
|
+
if (!entry.docPaths && entry.documentPath) {
|
|
193
|
+
return { ...entry, docPaths: [entry.documentPath] };
|
|
194
|
+
}
|
|
195
|
+
return entry;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return { ...parsed, entries: normalized };
|
|
187
199
|
} catch (error) {
|
|
188
200
|
console.warn("Could not read the history:", error.message);
|
|
189
201
|
return { entries: [] };
|
|
@@ -3,9 +3,12 @@ import path from "node:path";
|
|
|
3
3
|
import remarkGfm from "remark-gfm";
|
|
4
4
|
import remarkLint from "remark-lint";
|
|
5
5
|
import remarkParse from "remark-parse";
|
|
6
|
+
import { isRelative } from "ufo";
|
|
6
7
|
import { unified } from "unified";
|
|
7
8
|
import { visit } from "unist-util-visit";
|
|
8
9
|
import { VFile } from "vfile";
|
|
10
|
+
|
|
11
|
+
import { isRemoteFile, isRemoteFileAvailable } from "./file-utils.mjs";
|
|
9
12
|
import { validateMermaidSyntax } from "./mermaid-validator.mjs";
|
|
10
13
|
|
|
11
14
|
/**
|
|
@@ -232,6 +235,34 @@ function checkLocalImages(markdown, source, errorMessages, markdownFilePath, bas
|
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
|
|
238
|
+
async function checkRemoteImages(markdown, source, errorMessages) {
|
|
239
|
+
const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
240
|
+
let match;
|
|
241
|
+
|
|
242
|
+
while (true) {
|
|
243
|
+
match = imageRegex.exec(markdown);
|
|
244
|
+
if (match === null) break;
|
|
245
|
+
const imagePath = match[2].trim();
|
|
246
|
+
const altText = match[1];
|
|
247
|
+
|
|
248
|
+
if (isRelative(imagePath)) continue;
|
|
249
|
+
if (imagePath.startsWith("/")) continue;
|
|
250
|
+
|
|
251
|
+
// Skip data URLs
|
|
252
|
+
if (/^data:/.test(imagePath)) continue;
|
|
253
|
+
|
|
254
|
+
if (isRemoteFile(imagePath)) {
|
|
255
|
+
const isAvailable = await isRemoteFileAvailable(imagePath);
|
|
256
|
+
if (isAvailable) continue;
|
|
257
|
+
else {
|
|
258
|
+
errorMessages.push(
|
|
259
|
+
`Found invalid remote image in ${source}:  - only valid media resources can be used`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
235
266
|
/**
|
|
236
267
|
* Check content structure and formatting issues
|
|
237
268
|
* @param {string} markdown - The markdown content
|
|
@@ -370,7 +401,10 @@ export async function checkMarkdown(markdown, source = "content", options = {})
|
|
|
370
401
|
// 2. Check local images existence
|
|
371
402
|
checkLocalImages(markdown, source, errorMessages, filePath, baseDir);
|
|
372
403
|
|
|
373
|
-
// 3. Check
|
|
404
|
+
// 3. Check remote images existence
|
|
405
|
+
await checkRemoteImages(markdown, source, errorMessages);
|
|
406
|
+
|
|
407
|
+
// 4. Check content structure and formatting issues
|
|
374
408
|
checkContentStructure(markdown, source, errorMessages);
|
|
375
409
|
|
|
376
410
|
// Check mermaid code blocks and other custom validations
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<media_handling_rules>
|
|
2
|
-
Media resource usage rules:
|
|
3
|
-
|
|
4
|
-
- When DataSources contain media resource files, incorporate them appropriately in the generated content
|
|
5
|
-
- Media resources are provided in markdown format, example: 
|
|
6
|
-
- Display images in markdown format within generated results
|
|
7
|
-
- Based on resource descriptions, place images strategically in contextually relevant positions to enhance the presentation
|
|
8
|
-
- To ensure correct media resource paths, **only use media resources provided in media_list or remote URL media resources**
|
|
9
|
-
</media_handling_rules>
|