@aigne/doc-smith 0.0.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.
- package/README.md +87 -0
- package/agents/batch-docs-detail-generator.yaml +14 -0
- package/agents/batch-translate.yaml +44 -0
- package/agents/check-detail-generated.mjs +128 -0
- package/agents/check-detail-result.mjs +141 -0
- package/agents/check-structure-planning-result.yaml +30 -0
- package/agents/check-structure-planning.mjs +54 -0
- package/agents/content-detail-generator.yaml +50 -0
- package/agents/detail-generator-and-translate.yaml +88 -0
- package/agents/detail-regenerator.yaml +107 -0
- package/agents/docs-generator.yaml +93 -0
- package/agents/format-structure-plan.mjs +23 -0
- package/agents/input-generator.mjs +142 -0
- package/agents/load-sources.mjs +329 -0
- package/agents/publish-docs.mjs +212 -0
- package/agents/reflective-structure-planner.yaml +10 -0
- package/agents/save-docs.mjs +153 -0
- package/agents/save-output.mjs +25 -0
- package/agents/save-single-doc.mjs +18 -0
- package/agents/schema/structure-plan-result.yaml +32 -0
- package/agents/schema/structure-plan.yaml +26 -0
- package/agents/structure-planning.yaml +49 -0
- package/agents/transform-detail-datasources.mjs +14 -0
- package/agents/translate.yaml +28 -0
- package/aigne.yaml +28 -0
- package/biome.json +51 -0
- package/docs-mcp/aigne.yaml +8 -0
- package/docs-mcp/get-docs-detail.mjs +42 -0
- package/docs-mcp/get-docs-structure.mjs +11 -0
- package/package.json +33 -0
- package/prompts/check-structure-planning-result.md +82 -0
- package/prompts/content-detail-generator.md +99 -0
- package/prompts/document/detail-example.md +441 -0
- package/prompts/document/detail-generator.md +95 -0
- package/prompts/document/structure-example.md +98 -0
- package/prompts/document/structure-getting-started.md +10 -0
- package/prompts/document/structure-planning.md +17 -0
- package/prompts/structure-planning.md +108 -0
- package/prompts/translator.md +69 -0
- package/tests/README.md +93 -0
- package/tests/check-detail-result.test.mjs +103 -0
- package/tests/load-sources.test.mjs +642 -0
- package/tests/test-save-docs.mjs +132 -0
- package/utils/utils.mjs +86 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { joinURL } from "ufo";
|
|
3
|
+
import open from "open";
|
|
4
|
+
import { publishDocs as publishDocsFn } from "@aigne/publish-docs";
|
|
5
|
+
import { createConnect } from "@aigne/cli/utils/load-aigne.js";
|
|
6
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { parse, stringify } from "yaml";
|
|
10
|
+
import { execSync } from "node:child_process";
|
|
11
|
+
import { basename } from "node:path";
|
|
12
|
+
|
|
13
|
+
const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get project name from git repository or current directory
|
|
17
|
+
* @returns {string} - The project name
|
|
18
|
+
*/
|
|
19
|
+
function getProjectName() {
|
|
20
|
+
// Check if we're in a git repository
|
|
21
|
+
try {
|
|
22
|
+
const gitRemote = execSync("git remote get-url origin", {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
25
|
+
}).trim();
|
|
26
|
+
|
|
27
|
+
// Extract repository name from git remote URL
|
|
28
|
+
const repoName = gitRemote.split("/").pop().replace(".git", "");
|
|
29
|
+
return repoName;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// Not in git repository or no origin remote, use current directory name
|
|
32
|
+
return basename(process.cwd());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get access token from environment, config file, or prompt user for authorization
|
|
38
|
+
* @param {string} appUrl - The application URL
|
|
39
|
+
* @returns {Promise<string>} - The access token
|
|
40
|
+
*/
|
|
41
|
+
async function getAccessToken(appUrl) {
|
|
42
|
+
const DOC_SMITH_ENV_FILE = join(
|
|
43
|
+
homedir(),
|
|
44
|
+
".aigne",
|
|
45
|
+
"doc-smith-connected.yaml"
|
|
46
|
+
);
|
|
47
|
+
const { hostname } = new URL(appUrl);
|
|
48
|
+
|
|
49
|
+
let accessToken = process.env.DOC_DISCUSS_KIT_ACCESS_TOKEN;
|
|
50
|
+
|
|
51
|
+
// Check if access token exists in environment or config file
|
|
52
|
+
if (!accessToken) {
|
|
53
|
+
try {
|
|
54
|
+
if (existsSync(DOC_SMITH_ENV_FILE)) {
|
|
55
|
+
const data = await readFile(DOC_SMITH_ENV_FILE, "utf8");
|
|
56
|
+
if (data.includes("DOC_DISCUSS_KIT_ACCESS_TOKEN")) {
|
|
57
|
+
const envs = parse(data);
|
|
58
|
+
if (envs[hostname] && envs[hostname].DOC_DISCUSS_KIT_ACCESS_TOKEN) {
|
|
59
|
+
accessToken = envs[hostname].DOC_DISCUSS_KIT_ACCESS_TOKEN;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.warn("Failed to read config file:", error.message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// If still no access token, prompt user to authorize
|
|
69
|
+
if (!accessToken) {
|
|
70
|
+
const DISCUSS_KIT_URL = appUrl;
|
|
71
|
+
const connectUrl = joinURL(
|
|
72
|
+
new URL(DISCUSS_KIT_URL).origin,
|
|
73
|
+
WELLKNOWN_SERVICE_PATH_PREFIX
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const result = await createConnect({
|
|
78
|
+
connectUrl: connectUrl,
|
|
79
|
+
connectAction: "gen-simple-access-key",
|
|
80
|
+
source: `@aigne/cli doc-smith connect to Discuss Kit`,
|
|
81
|
+
closeOnSuccess: true,
|
|
82
|
+
openPage: (pageUrl) => open(pageUrl),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
accessToken = result.accessKeySecret;
|
|
86
|
+
process.env.DOC_DISCUSS_KIT_ACCESS_TOKEN = accessToken;
|
|
87
|
+
|
|
88
|
+
// Save the access token to config file
|
|
89
|
+
const aigneDir = join(homedir(), ".aigne");
|
|
90
|
+
if (!existsSync(aigneDir)) {
|
|
91
|
+
mkdirSync(aigneDir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const existingConfig = existsSync(DOC_SMITH_ENV_FILE)
|
|
95
|
+
? parse(await readFile(DOC_SMITH_ENV_FILE, "utf8"))
|
|
96
|
+
: {};
|
|
97
|
+
|
|
98
|
+
await writeFile(
|
|
99
|
+
DOC_SMITH_ENV_FILE,
|
|
100
|
+
stringify({
|
|
101
|
+
...existingConfig,
|
|
102
|
+
[hostname]: {
|
|
103
|
+
DOC_DISCUSS_KIT_ACCESS_TOKEN: accessToken,
|
|
104
|
+
DOC_DISCUSS_KIT_URL: DISCUSS_KIT_URL,
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error("Failed to get access token:", error);
|
|
110
|
+
throw new Error(
|
|
111
|
+
"Failed to obtain access token. Please check your network connection and try again later."
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return accessToken;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Save boardId to input.yaml file if it was auto-created
|
|
121
|
+
* @param {string} boardId - The original boardId (may be empty)
|
|
122
|
+
* @param {string} newBoardId - The boardId returned from publishDocsFn
|
|
123
|
+
*/
|
|
124
|
+
async function saveBoardIdToInput(boardId, newBoardId) {
|
|
125
|
+
// Only save if boardId was auto-created
|
|
126
|
+
if (!boardId && newBoardId) {
|
|
127
|
+
try {
|
|
128
|
+
const docSmithDir = join(process.cwd(), "doc-smith");
|
|
129
|
+
if (!existsSync(docSmithDir)) {
|
|
130
|
+
mkdirSync(docSmithDir, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const inputFilePath = join(docSmithDir, "input.yaml");
|
|
134
|
+
let fileContent = "";
|
|
135
|
+
|
|
136
|
+
// Read existing file content if it exists
|
|
137
|
+
if (existsSync(inputFilePath)) {
|
|
138
|
+
fileContent = await readFile(inputFilePath, "utf8");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check if boardId already exists in the file
|
|
142
|
+
const boardIdRegex = /^boardId:\s*.*$/m;
|
|
143
|
+
const newBoardIdLine = `boardId: ${newBoardId}`;
|
|
144
|
+
|
|
145
|
+
if (boardIdRegex.test(fileContent)) {
|
|
146
|
+
// Replace existing boardId line
|
|
147
|
+
fileContent = fileContent.replace(boardIdRegex, newBoardIdLine);
|
|
148
|
+
} else {
|
|
149
|
+
// Add boardId to the end of file
|
|
150
|
+
if (fileContent && !fileContent.endsWith("\n")) {
|
|
151
|
+
fileContent += "\n";
|
|
152
|
+
}
|
|
153
|
+
fileContent += newBoardIdLine + "\n";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
await writeFile(inputFilePath, fileContent);
|
|
157
|
+
console.log(`Board ID saved to: ${inputFilePath}`);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.warn("Failed to save board ID to input.yaml:", error.message);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default async function publish({ docsDir, appUrl, boardId }) {
|
|
165
|
+
const accessToken = await getAccessToken(appUrl);
|
|
166
|
+
|
|
167
|
+
process.env.DOC_ROOT_DIR = docsDir;
|
|
168
|
+
|
|
169
|
+
const sidebarPath = join(docsDir, "_sidebar.md");
|
|
170
|
+
|
|
171
|
+
const boardName = boardId ? "" : getProjectName();
|
|
172
|
+
|
|
173
|
+
const { success, boardId: newBoardId } = await publishDocsFn({
|
|
174
|
+
sidebarPath,
|
|
175
|
+
accessToken,
|
|
176
|
+
appUrl,
|
|
177
|
+
boardId,
|
|
178
|
+
// If boardId is empty, use project name as boardName and auto create board
|
|
179
|
+
boardName,
|
|
180
|
+
autoCreateBoard: !boardId,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Save boardId to input.yaml if it was auto-created
|
|
184
|
+
await saveBoardIdToInput(boardId, newBoardId);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
publishResult: {
|
|
188
|
+
success,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
publish.input_schema = {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {
|
|
196
|
+
docsDir: {
|
|
197
|
+
type: "string",
|
|
198
|
+
description: "The directory of the docs",
|
|
199
|
+
},
|
|
200
|
+
appUrl: {
|
|
201
|
+
type: "string",
|
|
202
|
+
description: "The url of the app",
|
|
203
|
+
default:
|
|
204
|
+
// "https://bbqawfllzdt3pahkdsrsone6p3wpxcwp62vlabtawfu.did.abtnet.io",
|
|
205
|
+
"https://www.staging.arcblock.io",
|
|
206
|
+
},
|
|
207
|
+
boardId: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description: "The id of the board",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type: team
|
|
2
|
+
name: reflective-structure-planner
|
|
3
|
+
description: A team of agents that plan the structure of the documentation.
|
|
4
|
+
skills:
|
|
5
|
+
- structure-planning.yaml
|
|
6
|
+
reflection:
|
|
7
|
+
reviewer: check-structure-planning-result.yaml
|
|
8
|
+
is_approved: isValid
|
|
9
|
+
max_iterations: 3
|
|
10
|
+
return_last_on_max_iterations: true
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { writeFile, readdir, unlink } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {Object} params
|
|
6
|
+
* @param {Array<{path: string, content: string, title: string}>} params.structurePlan
|
|
7
|
+
* @param {string} params.docsDir
|
|
8
|
+
* @param {Array<string>} [params.translateLanguages] - Translation languages
|
|
9
|
+
* @returns {Promise<Array<{ path: string, success: boolean, error?: string }>>}
|
|
10
|
+
*/
|
|
11
|
+
export default async function saveDocs({
|
|
12
|
+
structurePlanResult: structurePlan,
|
|
13
|
+
docsDir,
|
|
14
|
+
translateLanguages = [],
|
|
15
|
+
}) {
|
|
16
|
+
const results = [];
|
|
17
|
+
|
|
18
|
+
// Generate _sidebar.md
|
|
19
|
+
try {
|
|
20
|
+
const sidebar = generateSidebar(structurePlan);
|
|
21
|
+
const sidebarPath = join(docsDir, "_sidebar.md");
|
|
22
|
+
await writeFile(sidebarPath, sidebar, "utf8");
|
|
23
|
+
results.push({ path: sidebarPath, success: true });
|
|
24
|
+
} catch (err) {
|
|
25
|
+
results.push({ path: "_sidebar.md", success: false, error: err.message });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Clean up invalid .md files that are no longer in the structure plan
|
|
29
|
+
try {
|
|
30
|
+
const cleanupResults = await cleanupInvalidFiles(
|
|
31
|
+
structurePlan,
|
|
32
|
+
docsDir,
|
|
33
|
+
translateLanguages
|
|
34
|
+
);
|
|
35
|
+
results.push(...cleanupResults);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
results.push({ path: "cleanup", success: false, error: err.message });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { saveDocsResult: results };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Clean up .md files that are no longer in the structure plan
|
|
45
|
+
* @param {Array<{path: string, title: string}>} structurePlan
|
|
46
|
+
* @param {string} docsDir
|
|
47
|
+
* @param {Array<string>} translateLanguages
|
|
48
|
+
* @returns {Promise<Array<{ path: string, success: boolean, error?: string }>>}
|
|
49
|
+
*/
|
|
50
|
+
async function cleanupInvalidFiles(structurePlan, docsDir, translateLanguages) {
|
|
51
|
+
const results = [];
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Get all .md files in docsDir
|
|
55
|
+
const files = await readdir(docsDir);
|
|
56
|
+
const mdFiles = files.filter((file) => file.endsWith(".md"));
|
|
57
|
+
|
|
58
|
+
// Generate expected file names from structure plan
|
|
59
|
+
const expectedFiles = new Set();
|
|
60
|
+
|
|
61
|
+
// Add main document files
|
|
62
|
+
for (const { path } of structurePlan) {
|
|
63
|
+
const flatName = path.replace(/^\//, "").replace(/\//g, "-");
|
|
64
|
+
const fileName = `${flatName}.md`;
|
|
65
|
+
expectedFiles.add(fileName);
|
|
66
|
+
|
|
67
|
+
// Add translation files for each language
|
|
68
|
+
for (const lang of translateLanguages) {
|
|
69
|
+
const translateFileName = `${flatName}.${lang}.md`;
|
|
70
|
+
expectedFiles.add(translateFileName);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Find files to delete (files that are not in expectedFiles and not _sidebar.md)
|
|
75
|
+
const filesToDelete = mdFiles.filter(
|
|
76
|
+
(file) => !expectedFiles.has(file) && file !== "_sidebar.md"
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Delete invalid files
|
|
80
|
+
for (const file of filesToDelete) {
|
|
81
|
+
try {
|
|
82
|
+
const filePath = join(docsDir, file);
|
|
83
|
+
await unlink(filePath);
|
|
84
|
+
results.push({
|
|
85
|
+
path: filePath,
|
|
86
|
+
success: true,
|
|
87
|
+
message: `Deleted invalid file: ${file}`,
|
|
88
|
+
});
|
|
89
|
+
} catch (err) {
|
|
90
|
+
results.push({
|
|
91
|
+
path: file,
|
|
92
|
+
success: false,
|
|
93
|
+
error: `Failed to delete ${file}: ${err.message}`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (filesToDelete.length > 0) {
|
|
99
|
+
console.log(
|
|
100
|
+
`Cleaned up ${filesToDelete.length} invalid .md files from ${docsDir}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
// If docsDir doesn't exist or can't be read, that's okay
|
|
105
|
+
if (err.code !== "ENOENT") {
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return results;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Generate sidebar content, support nested structure, and the order is consistent with structurePlan
|
|
114
|
+
function generateSidebar(structurePlan) {
|
|
115
|
+
// Build tree structure
|
|
116
|
+
const root = {};
|
|
117
|
+
for (const { path, title, parentId } of structurePlan) {
|
|
118
|
+
const relPath = path.replace(/^\//, "");
|
|
119
|
+
const segments = relPath.split("/");
|
|
120
|
+
let node = root;
|
|
121
|
+
for (let i = 0; i < segments.length; i++) {
|
|
122
|
+
const seg = segments[i];
|
|
123
|
+
if (!node[seg])
|
|
124
|
+
node[seg] = {
|
|
125
|
+
__children: {},
|
|
126
|
+
__title: null,
|
|
127
|
+
__fullPath: segments.slice(0, i + 1).join("/"),
|
|
128
|
+
__parentId: parentId,
|
|
129
|
+
};
|
|
130
|
+
if (i === segments.length - 1) node[seg].__title = title;
|
|
131
|
+
node = node[seg].__children;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Recursively generate sidebar text, the link path is the flattened file name
|
|
135
|
+
function walk(node, parentSegments = [], indent = "") {
|
|
136
|
+
let out = "";
|
|
137
|
+
for (const key of Object.keys(node)) {
|
|
138
|
+
const item = node[key];
|
|
139
|
+
const fullSegments = [...parentSegments, key];
|
|
140
|
+
const flatFile = fullSegments.join("-") + ".md";
|
|
141
|
+
if (item.__title) {
|
|
142
|
+
const realIndent = item.__parentId === null ? "" : indent;
|
|
143
|
+
out += `${realIndent}* [${item.__title}](/${flatFile})\n`;
|
|
144
|
+
}
|
|
145
|
+
const children = item.__children;
|
|
146
|
+
if (Object.keys(children).length > 0) {
|
|
147
|
+
out += walk(children, fullSegments, `${indent} `);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
return walk(root).replace(/\n+$/, "");
|
|
153
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export default async function saveOutput({ savePath, fileName, saveKey, ...rest }) {
|
|
5
|
+
console.log("saveOutput", savePath, fileName, saveKey, rest);
|
|
6
|
+
if (!(saveKey in rest)) {
|
|
7
|
+
console.warn(`saveKey "${saveKey}" not found in input, skip saving.`);
|
|
8
|
+
return {
|
|
9
|
+
saveOutputStatus: false,
|
|
10
|
+
saveOutputPath: null,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const value = rest[saveKey];
|
|
15
|
+
const content =
|
|
16
|
+
typeof value === "object" && value !== null ? JSON.stringify(value, null, 2) : String(value);
|
|
17
|
+
await fs.mkdir(savePath, { recursive: true });
|
|
18
|
+
const filePath = join(savePath, fileName);
|
|
19
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
saveOutputStatus: true,
|
|
23
|
+
saveOutputPath: filePath,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { saveDocWithTranslations } from "../utils/utils.mjs";
|
|
2
|
+
|
|
3
|
+
export default async function saveSingleDoc({
|
|
4
|
+
path,
|
|
5
|
+
content,
|
|
6
|
+
docsDir,
|
|
7
|
+
translates,
|
|
8
|
+
labels,
|
|
9
|
+
}) {
|
|
10
|
+
const results = await saveDocWithTranslations({
|
|
11
|
+
path,
|
|
12
|
+
content,
|
|
13
|
+
docsDir,
|
|
14
|
+
translates,
|
|
15
|
+
labels,
|
|
16
|
+
});
|
|
17
|
+
return { saveSingleDocResult: results };
|
|
18
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type: array
|
|
2
|
+
items:
|
|
3
|
+
type: object
|
|
4
|
+
properties:
|
|
5
|
+
title:
|
|
6
|
+
type: string
|
|
7
|
+
description:
|
|
8
|
+
type: string
|
|
9
|
+
path:
|
|
10
|
+
type: string
|
|
11
|
+
description: 路径,以 RUL 的格式返回,不能为空, 必须以 / 开头,不需要包含语言层级,比如 /zh/about 直接返回 /about
|
|
12
|
+
parentId:
|
|
13
|
+
type:
|
|
14
|
+
- string
|
|
15
|
+
- "null"
|
|
16
|
+
translates:
|
|
17
|
+
type: array
|
|
18
|
+
items:
|
|
19
|
+
type: object
|
|
20
|
+
properties:
|
|
21
|
+
language:
|
|
22
|
+
type: string
|
|
23
|
+
description: 语言代码,比如 zh, en, ja 等
|
|
24
|
+
sourceIds:
|
|
25
|
+
type: array
|
|
26
|
+
items:
|
|
27
|
+
type: string
|
|
28
|
+
description: 关联的 dataSources 中的 sourceId,用于后续的翻译和内容生成,必须来自 datasources 中的 sourceId,不能有虚假的 id, 不能为空
|
|
29
|
+
required:
|
|
30
|
+
- title
|
|
31
|
+
- description
|
|
32
|
+
- path
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type: array
|
|
2
|
+
items:
|
|
3
|
+
type: object
|
|
4
|
+
properties:
|
|
5
|
+
title:
|
|
6
|
+
type: string
|
|
7
|
+
description:
|
|
8
|
+
type: string
|
|
9
|
+
path:
|
|
10
|
+
type: string
|
|
11
|
+
description: 路径,以 RUL 的格式返回,不能为空, 必须以 / 开头,不需要包含语言层级,比如 /zh/about 直接返回 /about
|
|
12
|
+
parentId:
|
|
13
|
+
type:
|
|
14
|
+
- string
|
|
15
|
+
- "null"
|
|
16
|
+
description: 父节点 path,如果为 null 则表示是顶级节点
|
|
17
|
+
sourceIds:
|
|
18
|
+
type: array
|
|
19
|
+
description: 关联的 dataSources 中的 sourceId,用于后续的翻译和内容生成,必须来自 datasources 中的 sourceId,不能有虚假的 id, **不能为空**
|
|
20
|
+
items:
|
|
21
|
+
type: string
|
|
22
|
+
required:
|
|
23
|
+
- title
|
|
24
|
+
- description
|
|
25
|
+
- path
|
|
26
|
+
- sourceIds
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: structure-planning
|
|
2
|
+
description: 通用结构规划生成器,支持网站、文档、书籍、演示文稿等多种场景
|
|
3
|
+
instructions:
|
|
4
|
+
url: ../prompts/structure-planning.md
|
|
5
|
+
input_schema:
|
|
6
|
+
type: object
|
|
7
|
+
properties:
|
|
8
|
+
rules:
|
|
9
|
+
type: string
|
|
10
|
+
description: 用户的结构规划的要求
|
|
11
|
+
locale:
|
|
12
|
+
type: string
|
|
13
|
+
description: 用户语言,如 zh、en
|
|
14
|
+
datasources:
|
|
15
|
+
type: string
|
|
16
|
+
description: 结构规划的上下文,用于辅助结构规划
|
|
17
|
+
targetAudience:
|
|
18
|
+
type: string
|
|
19
|
+
description: 结构规划的目标受众
|
|
20
|
+
nodeName:
|
|
21
|
+
type: string
|
|
22
|
+
description: 结构规划的节点名称
|
|
23
|
+
glossary:
|
|
24
|
+
type: string
|
|
25
|
+
description: 术语表
|
|
26
|
+
structurePlanFeedback:
|
|
27
|
+
type: string
|
|
28
|
+
description: 结构规划的反馈
|
|
29
|
+
docsType:
|
|
30
|
+
type: string
|
|
31
|
+
description: 文档类型,支持:general、getting-started、reference、faq
|
|
32
|
+
default: general
|
|
33
|
+
required:
|
|
34
|
+
- rules
|
|
35
|
+
- datasources
|
|
36
|
+
output_schema:
|
|
37
|
+
type: object
|
|
38
|
+
properties:
|
|
39
|
+
structurePlan: ./schema/structure-plan.yaml
|
|
40
|
+
structurePlanTree:
|
|
41
|
+
type: string
|
|
42
|
+
description: |
|
|
43
|
+
结构规划的树形结构,用于展示结构规划的层级关系,每一级有不同的缩进,方便用户直观查看结构规划的层级关系,参考格式:
|
|
44
|
+
```
|
|
45
|
+
- 首页
|
|
46
|
+
- 产品
|
|
47
|
+
- 产品详情
|
|
48
|
+
- 产品介绍
|
|
49
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default function transformDetailDatasources({
|
|
2
|
+
sourceIds,
|
|
3
|
+
datasourcesList,
|
|
4
|
+
}) {
|
|
5
|
+
// Build a map for fast lookup
|
|
6
|
+
const dsMap = Object.fromEntries(
|
|
7
|
+
(datasourcesList || []).map((ds) => [ds.sourceId, ds.content])
|
|
8
|
+
);
|
|
9
|
+
// Collect formatted contents in order
|
|
10
|
+
const contents = (sourceIds || [])
|
|
11
|
+
.filter((id) => dsMap[id])
|
|
12
|
+
.map((id) => `// sourceId: ${id}\n${dsMap[id]}\n`);
|
|
13
|
+
return { detailDataSources: contents.join("") };
|
|
14
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: translate
|
|
2
|
+
description: Translate the wiki article to another language
|
|
3
|
+
instructions:
|
|
4
|
+
url: ../prompts/translator.md
|
|
5
|
+
input_schema:
|
|
6
|
+
type: object
|
|
7
|
+
properties:
|
|
8
|
+
language:
|
|
9
|
+
type: string
|
|
10
|
+
description: Language to translate the article into (e.g., 'zh_CN' for Chinese)
|
|
11
|
+
content:
|
|
12
|
+
type: string
|
|
13
|
+
description: Content to translate
|
|
14
|
+
glossary:
|
|
15
|
+
type: string
|
|
16
|
+
description: 术语表
|
|
17
|
+
required:
|
|
18
|
+
- language
|
|
19
|
+
- content
|
|
20
|
+
output_schema:
|
|
21
|
+
type: object
|
|
22
|
+
properties:
|
|
23
|
+
translation:
|
|
24
|
+
type: string
|
|
25
|
+
description: Translation of the content
|
|
26
|
+
language:
|
|
27
|
+
type: string
|
|
28
|
+
description: Language of the translation
|
package/aigne.yaml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env aigne
|
|
2
|
+
|
|
3
|
+
chat_model:
|
|
4
|
+
provider: gemini
|
|
5
|
+
name: gemini-2.5-pro-preview-05-06
|
|
6
|
+
# name: gemini-2.5-flash-preview-05-20
|
|
7
|
+
temperature: 0.8
|
|
8
|
+
agents:
|
|
9
|
+
- ./agents/docs-generator.yaml
|
|
10
|
+
- ./agents/structure-planning.yaml
|
|
11
|
+
- ./agents/batch-docs-detail-generator.yaml
|
|
12
|
+
- ./agents/load-sources.mjs
|
|
13
|
+
- ./agents/save-docs.mjs
|
|
14
|
+
- ./agents/translate.yaml
|
|
15
|
+
- ./agents/detail-generator-and-translate.yaml
|
|
16
|
+
- ./agents/check-detail-generated.mjs
|
|
17
|
+
- ./agents/transform-detail-datasources.mjs
|
|
18
|
+
- ./agents/batch-translate.yaml
|
|
19
|
+
- ./agents/save-single-doc.mjs
|
|
20
|
+
- ./agents/save-output.mjs
|
|
21
|
+
- ./agents/check-structure-planning.mjs
|
|
22
|
+
- ./agents/content-detail-generator.yaml
|
|
23
|
+
- ./agents/reflective-structure-planner.yaml
|
|
24
|
+
- ./agents/check-structure-planning-result.yaml
|
|
25
|
+
- ./agents/input-generator.mjs
|
|
26
|
+
- ./agents/detail-regenerator.yaml
|
|
27
|
+
- ./agents/publish-docs.mjs
|
|
28
|
+
- ./agents/format-structure-plan.mjs
|
package/biome.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": true,
|
|
10
|
+
"experimentalScannerIgnores": ["node_modules"]
|
|
11
|
+
},
|
|
12
|
+
"formatter": {
|
|
13
|
+
"enabled": true,
|
|
14
|
+
"indentStyle": "space",
|
|
15
|
+
"lineWidth": 100
|
|
16
|
+
},
|
|
17
|
+
"linter": {
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"rules": {
|
|
20
|
+
"recommended": true,
|
|
21
|
+
"correctness": {
|
|
22
|
+
"useImportExtensions": "error",
|
|
23
|
+
"noUnusedVariables": "error",
|
|
24
|
+
"noUnusedImports": "error",
|
|
25
|
+
"noUnusedFunctionParameters": "error",
|
|
26
|
+
"noUnusedPrivateClassMembers": "error"
|
|
27
|
+
},
|
|
28
|
+
"suspicious": {
|
|
29
|
+
"noConfusingVoidType": "off",
|
|
30
|
+
"noExplicitAny": "off"
|
|
31
|
+
},
|
|
32
|
+
"complexity": {
|
|
33
|
+
"noForEach": "off",
|
|
34
|
+
"useLiteralKeys": "off"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"javascript": {
|
|
39
|
+
"formatter": {
|
|
40
|
+
"quoteStyle": "double"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"assist": {
|
|
44
|
+
"enabled": true,
|
|
45
|
+
"actions": {
|
|
46
|
+
"source": {
|
|
47
|
+
"organizeImports": "on"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|