czon 0.5.6 → 0.5.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/README.md +4 -4
- package/dist/build/pipeline.js +1 -1
- package/dist/build/robots.js +2 -1
- package/dist/process/processTranslations.js +43 -17
- package/dist/services/opencode.js +89 -58
- package/package.json +2 -1
- package/tsconfig.json +3 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- **C**: **C**ontent oriented | 内容为王,专注内容
|
|
6
6
|
- **Z**: **Z**ero Configuration | 零配置写作,减少打扰
|
|
7
7
|
- **O**: **O**rganic AI-Native | 有机的 AI 原生,深度集成 AI
|
|
8
|
-
- **N**: **N**-
|
|
8
|
+
- **N**: **N**-shaped Energy Curve | N 型能量曲线,介入创作-分发-反馈的各个环节
|
|
9
9
|
|
|
10
10
|
[> Website Demo](https://czon.zccz14.com/)
|
|
11
11
|
|
|
@@ -26,9 +26,9 @@ AI 时代下,作为网站内容创作者,我们可以有更智能的内容
|
|
|
26
26
|
## 基本功能
|
|
27
27
|
|
|
28
28
|
1. 🌍 **自动多语言翻译**:使用 AI 进行增量翻译,让用户使用母语编写 Markdown,但是用户可以是多语言的。
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
2. 💭 **自动摘要提取**:使用 AI 对原始文本进行内容分析和提取。
|
|
30
|
+
3. 🏷️ **自动标签分类**:使用 AI 对内容进行标签和分类的提取和管理。
|
|
31
|
+
4. 🧭 **智能分类导航**:使用 AI 生成站点地图和导航,源文件的位置不敏感。
|
|
32
32
|
|
|
33
33
|
## 静态站点生成 (SSG)
|
|
34
34
|
|
package/dist/build/pipeline.js
CHANGED
|
@@ -73,7 +73,7 @@ async function buildPipeline(options) {
|
|
|
73
73
|
// 清理输出目录
|
|
74
74
|
await fs.rm(paths_1.CZON_DIST_DIR, { recursive: true, force: true });
|
|
75
75
|
// 确保 .czon/.gitignore 文件
|
|
76
|
-
await (0, writeFile_1.writeFile)(path.join(paths_1.CZON_DIR, '.gitignore'), 'dist\n');
|
|
76
|
+
await (0, writeFile_1.writeFile)(path.join(paths_1.CZON_DIR, '.gitignore'), 'dist\ntmp\n');
|
|
77
77
|
// 扫描源文件
|
|
78
78
|
await (0, scanSourceFiles_1.scanSourceFiles)();
|
|
79
79
|
// 写入 .raw 目录用于存储原始文件 (非翻译文件)
|
package/dist/build/robots.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.generateRobotsTxt = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
+
const metadata_1 = require("../metadata");
|
|
38
39
|
const paths_1 = require("../paths");
|
|
39
40
|
const writeFile_1 = require("../utils/writeFile");
|
|
40
41
|
const generateRobotsTxt = async () => {
|
|
@@ -106,7 +107,7 @@ Disallow:
|
|
|
106
107
|
|
|
107
108
|
Content-Signal: ai-train=yes, search=yes, ai-input=yes
|
|
108
109
|
|
|
109
|
-
Sitemap:
|
|
110
|
+
${metadata_1.MetaData.options.baseUrl ? `Sitemap: ${path.join(metadata_1.MetaData.options.baseUrl, 'sitemap.xml')}` : ''}
|
|
110
111
|
`;
|
|
111
112
|
const robotsPath = path.join(paths_1.CZON_DIST_DIR, 'robots.txt');
|
|
112
113
|
await (0, writeFile_1.writeFile)(robotsPath, robotsTxtContent);
|
|
@@ -45,18 +45,46 @@ const sha256_1 = require("../utils/sha256");
|
|
|
45
45
|
const writeFile_1 = require("../utils/writeFile");
|
|
46
46
|
async function translateWithOpenCode(sourcePath, targetPath, targetLang, options) {
|
|
47
47
|
const langName = languages_1.LANGUAGE_NAMES[targetLang];
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
const taskId = crypto.randomUUID();
|
|
49
|
+
const COMMENT_FILE = (0, path_1.join)(paths_1.CZON_DIR, 'tmp', `comment-${taskId}.txt`);
|
|
50
|
+
// 清理旧的评论文件
|
|
51
|
+
await (0, writeFile_1.writeFile)(COMMENT_FILE, '');
|
|
52
|
+
// 任务定义:每个任务包含提示、代理名称和描述
|
|
53
|
+
const tasks = [
|
|
54
|
+
{
|
|
55
|
+
description: '翻译任务',
|
|
56
|
+
prompt: `将 ${sourcePath} 翻译成 ${langName} (${targetLang}) 并保存为 ${targetPath}。阅读 ${COMMENT_FILE} (如果存在) 以了解改进建议。`,
|
|
57
|
+
model: 'deepseek/deepseek-chat',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
description: '翻译质量评估任务',
|
|
61
|
+
prompt: `请判断 ${targetPath} 是否是 ${sourcePath} 的优秀翻译。评估标准包括准确性、流畅性、术语一致性、文化适应性。输出评估结果和分数(0-10分)。将评审意见保存到 ${COMMENT_FILE} 文件中。如果通过评估,请写入 "Result: Passed"。否则写入 "Result: Failed" 并附上改进建议。`,
|
|
62
|
+
model: 'deepseek/deepseek-chat',
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
for (let it = 0; it < 10; it++) {
|
|
66
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
67
|
+
const task = tasks[i];
|
|
68
|
+
console.log(`\n=== 任务 ${i + 1}: ${task.description} ===`);
|
|
69
|
+
console.log(`提示: ${task.prompt}`);
|
|
70
|
+
await (0, opencode_1.runOpenCode)(task.prompt, { model: task.model });
|
|
71
|
+
console.log(`✅ 任务 ${i + 1} 完成`);
|
|
72
|
+
}
|
|
73
|
+
const reviewFileContent = await (0, promises_1.readFile)(COMMENT_FILE, 'utf-8').catch(() => '');
|
|
74
|
+
// 检查评审结果是否通过
|
|
75
|
+
if (reviewFileContent.includes('Result: Passed') &&
|
|
76
|
+
!reviewFileContent.includes('Result: Failed')) {
|
|
77
|
+
console.log('\n=== 翻译质量评估通过,所有任务完成 ===');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
57
80
|
}
|
|
58
|
-
return (0, promises_1.readFile)(targetPath, 'utf-8');
|
|
59
81
|
}
|
|
82
|
+
const translateWithLLMCall = async (sourcePath, targetPath, lang) => {
|
|
83
|
+
const content = await (0, promises_1.readFile)(sourcePath, 'utf-8');
|
|
84
|
+
const translatedResponse = await (0, translateMarkdown_1.translateMarkdown)(sourcePath, content, lang);
|
|
85
|
+
const translatedContent = translatedResponse.choices?.[0].message.content?.trim() || '';
|
|
86
|
+
await (0, writeFile_1.writeFile)(targetPath, translatedContent);
|
|
87
|
+
};
|
|
60
88
|
/**
|
|
61
89
|
* 处理翻译
|
|
62
90
|
*/
|
|
@@ -69,7 +97,6 @@ async function processTranslations() {
|
|
|
69
97
|
return;
|
|
70
98
|
}
|
|
71
99
|
return Promise.all(langs.map(async (lang) => {
|
|
72
|
-
var _a;
|
|
73
100
|
if (verbose)
|
|
74
101
|
console.info(`📄 Processing file for translation: ${file.path}`);
|
|
75
102
|
if (!file.metadata) {
|
|
@@ -95,12 +122,11 @@ async function processTranslations() {
|
|
|
95
122
|
console.info(`ℹ️ Content unchanged for ${file.path}, skipping translation.`);
|
|
96
123
|
return;
|
|
97
124
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const translationMeta = ((
|
|
101
|
-
translationMeta.content_length = translatedContent.length; // 记录翻译后内容长度
|
|
102
|
-
translationMeta.token_used = translatedResponse.usage; // 记录 token 使用情况
|
|
103
|
-
await (0, writeFile_1.writeFile)(targetPath, translatedContent);
|
|
125
|
+
// await translateWithLLMCall(sourcePath, targetPath, lang);
|
|
126
|
+
await translateWithOpenCode(sourcePath, targetPath, lang);
|
|
127
|
+
// const translationMeta = ((file.translations ??= {})[lang] ??= {});
|
|
128
|
+
// translationMeta.content_length = translatedContent.length; // 记录翻译后内容长度
|
|
129
|
+
// translationMeta.token_used = translatedResponse.usage; // 记录 token 使用情况
|
|
104
130
|
// 存储已增强内容的哈希值
|
|
105
131
|
file.nativeMarkdownHash = hash;
|
|
106
132
|
if (verbose)
|
|
@@ -1,86 +1,117 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.installAgentsToGlobal = exports.runOpenCode = void 0;
|
|
4
|
-
const child_process_1 = require("child_process");
|
|
5
4
|
const promises_1 = require("fs/promises");
|
|
6
5
|
const path_1 = require("path");
|
|
7
6
|
const metadata_1 = require("../metadata");
|
|
8
7
|
const paths_1 = require("../paths");
|
|
9
8
|
const writeFile_1 = require("../utils/writeFile");
|
|
9
|
+
function parseModelString(model) {
|
|
10
|
+
const parts = model.split('/');
|
|
11
|
+
if (parts.length === 2) {
|
|
12
|
+
return { providerID: parts[0], modelID: parts[1] };
|
|
13
|
+
}
|
|
14
|
+
// Default provider if no slash
|
|
15
|
+
return { providerID: 'opencode', modelID: model };
|
|
16
|
+
}
|
|
10
17
|
/**
|
|
11
|
-
* Run OpenCode
|
|
18
|
+
* Run OpenCode to generate AI response for a given prompt.
|
|
12
19
|
*
|
|
13
|
-
* This function
|
|
14
|
-
*
|
|
20
|
+
* This function uses the OpenCode SDK to connect to a running OpenCode server.
|
|
21
|
+
* Assumes an OpenCode server is already running externally.
|
|
15
22
|
*
|
|
16
23
|
* @param prompt - The prompt to send to OpenCode
|
|
17
24
|
* @param options - Optional configuration
|
|
18
|
-
* @returns Promise
|
|
25
|
+
* @returns Promise that resolves when the operation completes
|
|
19
26
|
*
|
|
20
27
|
* @important
|
|
21
|
-
* The
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* processed by downstream AI components (e.g., pass to another LLM call,
|
|
25
|
-
* write to a temp file for a Supervisor Agent, etc.).
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* const response = await runOpenCode('Explain closures in JavaScript');
|
|
29
|
-
* // response format is unpredictable - pass it to another AI for processing
|
|
30
|
-
* const analyzed = await completeMessages([
|
|
31
|
-
* { role: 'user', content: `Analyze this AI output:\n${response}` }
|
|
32
|
-
* ]);
|
|
28
|
+
* The AI response is handled internally by OpenCode. This function does not
|
|
29
|
+
* return the response content. Any output files or results are managed by
|
|
30
|
+
* the OpenCode agent or session directly.
|
|
33
31
|
*/
|
|
34
32
|
const runOpenCode = (prompt, options) => {
|
|
35
33
|
const model = options?.model ?? 'opencode/gpt-5-nano';
|
|
36
34
|
const signal = options?.signal;
|
|
37
|
-
const cwd = options?.cwd;
|
|
35
|
+
const cwd = options?.cwd || process.cwd();
|
|
36
|
+
const agent = options?.agent;
|
|
38
37
|
const verbose = metadata_1.MetaData.options.verbose;
|
|
39
38
|
if (verbose) {
|
|
40
|
-
console.info(`🛠️ Running OpenCode with model: ${model}, prompt: ${prompt}`);
|
|
39
|
+
console.info(`🛠️ Running OpenCode with model: ${model}, agent: ${agent || 'none'}, prompt: ${prompt}`);
|
|
41
40
|
}
|
|
42
|
-
return new Promise((resolve, reject) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
model,
|
|
50
|
-
...(options?.agent ? ['--agent', options.agent] : []),
|
|
51
|
-
'--format',
|
|
52
|
-
'json',
|
|
53
|
-
], {
|
|
54
|
-
stdio: ['ignore', 'pipe', 'inherit'],
|
|
55
|
-
cwd,
|
|
56
|
-
env: Object.assign({
|
|
57
|
-
OPENCODE_PERMISSION: JSON.stringify({ bash: 'allow', read: 'allow', write: 'allow' }),
|
|
58
|
-
}, process.env),
|
|
59
|
-
});
|
|
60
|
-
let output = '';
|
|
61
|
-
proc.stdout.on('data', data => {
|
|
62
|
-
const chunk = data.toString();
|
|
63
|
-
output += chunk;
|
|
64
|
-
if (verbose) {
|
|
65
|
-
console.info('OpenCode stdout chunk:', chunk);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
proc.on('error', err => {
|
|
69
|
-
reject(new Error(`Failed to start OpenCode process: ${err.message}`));
|
|
70
|
-
});
|
|
71
|
-
proc.on('close', code => {
|
|
72
|
-
if (code === 0) {
|
|
73
|
-
resolve(output.trim());
|
|
41
|
+
return new Promise(async (resolve, reject) => {
|
|
42
|
+
const agentInfo = agent ? ` with agent ${agent}` : '';
|
|
43
|
+
console.info(`🚀 Running OpenCode with model ${model}${agentInfo}`);
|
|
44
|
+
let cancelled = false;
|
|
45
|
+
const cleanup = () => {
|
|
46
|
+
if (signal) {
|
|
47
|
+
signal.removeEventListener('abort', onAbort);
|
|
74
48
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
49
|
+
};
|
|
50
|
+
const onAbort = () => {
|
|
51
|
+
cancelled = true;
|
|
52
|
+
cleanup();
|
|
53
|
+
reject(new Error('OpenCode execution was aborted'));
|
|
54
|
+
};
|
|
79
55
|
if (signal) {
|
|
80
|
-
signal.addEventListener('abort',
|
|
81
|
-
|
|
82
|
-
|
|
56
|
+
signal.addEventListener('abort', onAbort);
|
|
57
|
+
if (signal.aborted) {
|
|
58
|
+
onAbort();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const { createOpencodeClient } = await import('@opencode-ai/sdk');
|
|
64
|
+
const baseUrl = 'http://localhost:4096';
|
|
65
|
+
const client = createOpencodeClient({
|
|
66
|
+
baseUrl: baseUrl,
|
|
67
|
+
directory: cwd,
|
|
83
68
|
});
|
|
69
|
+
const modelObj = parseModelString(model);
|
|
70
|
+
const session = await client.session.create();
|
|
71
|
+
if (!session.data?.id)
|
|
72
|
+
throw new Error('Failed to create OpenCode session', { cause: session.error });
|
|
73
|
+
const directoryBase64 = Buffer.from(session.data.directory).toString('base64');
|
|
74
|
+
const url = `${baseUrl}/${directoryBase64}/session/${session.data.id}`;
|
|
75
|
+
console.info('OpenCode Session Created', url);
|
|
76
|
+
const response = await client.session.prompt({
|
|
77
|
+
path: {
|
|
78
|
+
id: session.data.id,
|
|
79
|
+
},
|
|
80
|
+
body: {
|
|
81
|
+
model: modelObj,
|
|
82
|
+
agent,
|
|
83
|
+
parts: [
|
|
84
|
+
{
|
|
85
|
+
type: 'text',
|
|
86
|
+
text: prompt,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
query: {
|
|
91
|
+
directory: cwd,
|
|
92
|
+
},
|
|
93
|
+
signal,
|
|
94
|
+
});
|
|
95
|
+
if (cancelled) {
|
|
96
|
+
throw new Error('Cancelled');
|
|
97
|
+
}
|
|
98
|
+
if (response.error) {
|
|
99
|
+
throw new Error(`OpenCode API error: ${JSON.stringify(response.error)}`);
|
|
100
|
+
}
|
|
101
|
+
// await client.session.delete({
|
|
102
|
+
// path: {
|
|
103
|
+
// id: session.data.id,
|
|
104
|
+
// },
|
|
105
|
+
// });
|
|
106
|
+
cleanup();
|
|
107
|
+
resolve();
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
if (cancelled) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
cleanup();
|
|
114
|
+
reject(new Error(`OpenCode SDK error: ${err instanceof Error ? err.message : String(err)}. Make sure an OpenCode server is running.`));
|
|
84
115
|
}
|
|
85
116
|
});
|
|
86
117
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "czon",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "CZone - AI enhanced Markdown content engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"typescript": "^5.9.3"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
+
"@opencode-ai/sdk": "^1.1.34",
|
|
54
55
|
"@types/react": "^19.2.7",
|
|
55
56
|
"@types/react-dom": "^19.2.3",
|
|
56
57
|
"chokidar": "^5.0.0",
|
package/tsconfig.json
CHANGED