czon 0.5.6 → 0.6.0
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 +24 -13
- package/dist/build/robots.js +2 -1
- package/dist/process/category.js +5 -9
- package/dist/process/enhanceMarkdownSource.js +3 -24
- package/dist/process/processTranslations.js +45 -19
- package/dist/process/scanSourceFiles.js +7 -4
- package/dist/process/template.js +8 -40
- package/dist/services/opencode.js +97 -58
- package/dist/ssg/ContentPage.js +5 -5
- package/dist/ssg/IndexPage.js +1 -2
- package/dist/ssg/app.js +1 -1
- package/dist/ssg/components/ContentMeta.js +1 -1
- package/dist/ssg/components/Navigator.js +3 -3
- package/dist/utils/convertMarkdownToHtml.js +60 -1
- 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,24 +73,35 @@ 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'),
|
|
76
|
+
await (0, writeFile_1.writeFile)(path.join(paths_1.CZON_DIR, '.gitignore'), [
|
|
77
|
+
'dist',
|
|
78
|
+
'tmp',
|
|
79
|
+
// 忽略所有非 md 文件: 先忽略所有文件,再排除 Markdown 文件不忽略
|
|
80
|
+
'src/**/*.*',
|
|
81
|
+
'!src/**/*.md',
|
|
82
|
+
].join('\n'));
|
|
77
83
|
// 扫描源文件
|
|
78
84
|
await (0, scanSourceFiles_1.scanSourceFiles)();
|
|
79
|
-
//
|
|
85
|
+
// 链接资源文件 (非翻译文件)
|
|
80
86
|
for (const file of metadata_1.MetaData.files) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const targetPath = path.join(paths_1.
|
|
87
|
+
if (file.path.endsWith('.md'))
|
|
88
|
+
continue; // 仅处理非 Markdown 文件
|
|
89
|
+
for (const lang of metadata_1.MetaData.options.langs || []) {
|
|
90
|
+
// 创建硬链接以节省磁盘空间
|
|
91
|
+
const targetPath = path.join(paths_1.CZON_SRC_DIR, lang, file.path);
|
|
86
92
|
const sourcePath = path.join(paths_1.INPUT_DIR, file.path);
|
|
87
|
-
console.info(
|
|
88
|
-
|
|
89
|
-
await (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
console.warn(`⚠️ Failed to write raw content for file ${file.path}:`, error);
|
|
93
|
+
console.info(`🔗 Linking file ${file.path} to ${targetPath} ...`);
|
|
94
|
+
// 确保 link 成功
|
|
95
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
96
|
+
await fs.rm(targetPath, { force: true });
|
|
97
|
+
await fs.link(sourcePath, targetPath);
|
|
93
98
|
}
|
|
99
|
+
// 拷贝一份到 __raw__ 目录以供 dist 使用
|
|
100
|
+
const rawTargetPath = path.join(paths_1.CZON_DIST_RAW_CONTENT_DIR, file.path);
|
|
101
|
+
const rawSourcePath = path.join(paths_1.INPUT_DIR, file.path);
|
|
102
|
+
console.info(`📄 Copying raw file ${file.path} to ${rawTargetPath} ...`);
|
|
103
|
+
await fs.mkdir(path.dirname(rawTargetPath), { recursive: true });
|
|
104
|
+
await fs.copyFile(rawSourcePath, rawTargetPath);
|
|
94
105
|
}
|
|
95
106
|
// 运行 AI 元数据提取
|
|
96
107
|
await (0, extractMetadataByAI_1.extractMetadataByAI)();
|
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);
|
package/dist/process/category.js
CHANGED
|
@@ -6,7 +6,6 @@ const openai_1 = require("../services/openai");
|
|
|
6
6
|
const formatFileForCategoryExtraction = (file) => {
|
|
7
7
|
return [
|
|
8
8
|
//
|
|
9
|
-
`Hash: ${file.hash}`,
|
|
10
9
|
`Path: ${file.path}`,
|
|
11
10
|
`Metadata: ${JSON.stringify(file.metadata)}`,
|
|
12
11
|
].join('\n');
|
|
@@ -19,14 +18,11 @@ const processExtractCategory = async () => {
|
|
|
19
18
|
console.info('ℹ️ All files already have categories, skipping category extraction.');
|
|
20
19
|
return;
|
|
21
20
|
}
|
|
22
|
-
// 这意味着,只要有一个新文件,就需要重新生成所有类别标签
|
|
23
|
-
// 这倒也合理,因为类别标签是整体相关的
|
|
24
|
-
// 如果是内容改动导致的呢?
|
|
25
21
|
const markdownFiles = metadata_1.MetaData.files.filter(f => f.path.endsWith('.md') && f.metadata);
|
|
26
22
|
if (verbose) {
|
|
27
23
|
console.info(`📂 Extracting categories for ${markdownFiles.length} markdown files...`);
|
|
28
24
|
for (const file of markdownFiles) {
|
|
29
|
-
console.info(` - File: ${file.path}
|
|
25
|
+
console.info(` - File: ${file.path}`);
|
|
30
26
|
}
|
|
31
27
|
}
|
|
32
28
|
// 提取类别标签列表
|
|
@@ -46,7 +42,7 @@ const processExtractCategory = async () => {
|
|
|
46
42
|
'请优先考虑尚未被分类的文件。',
|
|
47
43
|
'请以 JSON 格式返回类别标签列表。',
|
|
48
44
|
'示例输出格式:',
|
|
49
|
-
'{ "mappings": { "
|
|
45
|
+
'{ "mappings": { "path1": "tag1", "path2": "tag2" } }',
|
|
50
46
|
].join('\n'),
|
|
51
47
|
},
|
|
52
48
|
{
|
|
@@ -66,9 +62,9 @@ const processExtractCategory = async () => {
|
|
|
66
62
|
const json = categories.choices[0].message.content;
|
|
67
63
|
const parsed = JSON.parse(json);
|
|
68
64
|
for (const file of metadata_1.MetaData.files) {
|
|
69
|
-
const
|
|
70
|
-
if (parsed.mappings[
|
|
71
|
-
file.category = parsed.mappings[
|
|
65
|
+
const path = file.path;
|
|
66
|
+
if (parsed.mappings[path]) {
|
|
67
|
+
file.category = parsed.mappings[path];
|
|
72
68
|
}
|
|
73
69
|
}
|
|
74
70
|
console.info('✅ Extracted categories:', categories.choices[0].message.content);
|
|
@@ -10,25 +10,6 @@ const metadata_1 = require("../metadata");
|
|
|
10
10
|
const paths_1 = require("../paths");
|
|
11
11
|
const frontmatter_1 = require("../utils/frontmatter");
|
|
12
12
|
const writeFile_1 = require("../utils/writeFile");
|
|
13
|
-
const replaceInnerLinks = (file, markdownContent) => {
|
|
14
|
-
let content = markdownContent;
|
|
15
|
-
for (const link of file.links) {
|
|
16
|
-
if (URL.canParse(link))
|
|
17
|
-
continue; // 跳过绝对 URL
|
|
18
|
-
const targetPath = path_1.default.resolve('/', path_1.default.dirname(file.path), link).slice(1);
|
|
19
|
-
const targetFile = metadata_1.MetaData.files.find(f => f.path === targetPath);
|
|
20
|
-
if (!targetFile) {
|
|
21
|
-
console.warn(`⚠️ Link target not found for ${link} in file ${file.path}`);
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
// 替换链接 (使用相对链接)
|
|
25
|
-
const targetLink = `czon://${targetFile.hash}`;
|
|
26
|
-
// 全局替换链接
|
|
27
|
-
const linksRegex = new RegExp(`\\]\\(${link.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\)`, 'g');
|
|
28
|
-
content = content.replace(linksRegex, `](${targetLink})`);
|
|
29
|
-
}
|
|
30
|
-
return content;
|
|
31
|
-
};
|
|
32
13
|
/**
|
|
33
14
|
* 存储母语文件到 .czon/src
|
|
34
15
|
*/
|
|
@@ -41,11 +22,9 @@ async function storeNativeFiles() {
|
|
|
41
22
|
continue;
|
|
42
23
|
}
|
|
43
24
|
try {
|
|
44
|
-
if (!file.hash)
|
|
45
|
-
throw new Error(`Missing hash`);
|
|
46
25
|
if (!file.metadata?.inferred_lang)
|
|
47
26
|
throw new Error(`Missing inferred language`);
|
|
48
|
-
const filePath = path_1.default.join(paths_1.CZON_SRC_DIR, file.metadata.inferred_lang, file.
|
|
27
|
+
const filePath = path_1.default.join(paths_1.CZON_SRC_DIR, file.metadata.inferred_lang, file.path);
|
|
49
28
|
const originalContent = await (0, promises_1.readFile)(path_1.default.join(paths_1.INPUT_DIR, file.path), 'utf-8');
|
|
50
29
|
// 增强 YAML Frontmatter
|
|
51
30
|
const enhancedContent = (0, frontmatter_1.updateFrontmatter)(originalContent, {
|
|
@@ -55,8 +34,8 @@ async function storeNativeFiles() {
|
|
|
55
34
|
date: file.metadata.inferred_date,
|
|
56
35
|
});
|
|
57
36
|
// 进行内链接替换, 将相对链接替换为基于 czon://hash 的链接
|
|
58
|
-
const replacedContent = replaceInnerLinks(file, enhancedContent);
|
|
59
|
-
await (0, writeFile_1.writeFile)(filePath,
|
|
37
|
+
// const replacedContent = replaceInnerLinks(file, enhancedContent);
|
|
38
|
+
await (0, writeFile_1.writeFile)(filePath, enhancedContent);
|
|
60
39
|
}
|
|
61
40
|
catch (error) {
|
|
62
41
|
console.warn(`⚠️ Failed to store native file ${file.path}:`, error);
|
|
@@ -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) {
|
|
@@ -79,8 +106,8 @@ async function processTranslations() {
|
|
|
79
106
|
if (verbose)
|
|
80
107
|
console.log(`🌐 Translating to ${lang}...`);
|
|
81
108
|
// 存储翻译文件到 .czon/src/{lang}
|
|
82
|
-
const sourcePath = path_1.default.join(paths_1.CZON_SRC_DIR, file.metadata.inferred_lang, file.
|
|
83
|
-
const targetPath = path_1.default.join(paths_1.CZON_SRC_DIR, lang, file.
|
|
109
|
+
const sourcePath = path_1.default.join(paths_1.CZON_SRC_DIR, file.metadata.inferred_lang, file.path); // 使用已经加强的母语文件路径
|
|
110
|
+
const targetPath = path_1.default.join(paths_1.CZON_SRC_DIR, lang, file.path);
|
|
84
111
|
try {
|
|
85
112
|
const content = await (0, promises_1.readFile)(sourcePath, 'utf-8');
|
|
86
113
|
if (file.metadata.inferred_lang === lang) {
|
|
@@ -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)
|
|
@@ -30,7 +30,7 @@ async function scanSourceFiles() {
|
|
|
30
30
|
for (const filePath of markdownFiles) {
|
|
31
31
|
queue.push(filePath);
|
|
32
32
|
}
|
|
33
|
-
const
|
|
33
|
+
const paths = new Set();
|
|
34
34
|
while (queue.length > 0) {
|
|
35
35
|
const relativePath = queue.shift();
|
|
36
36
|
const fullPath = path_1.default.join(paths_1.INPUT_DIR, relativePath);
|
|
@@ -51,12 +51,15 @@ async function scanSourceFiles() {
|
|
|
51
51
|
}
|
|
52
52
|
const contentBuffer = await (0, promises_1.readFile)(fullPath);
|
|
53
53
|
const hash = (0, sha256_1.sha256)(contentBuffer);
|
|
54
|
-
|
|
55
|
-
let meta = metadata_1.MetaData.files.find(f => f.
|
|
54
|
+
paths.add(relativePath);
|
|
55
|
+
let meta = metadata_1.MetaData.files.find(f => f.path === relativePath);
|
|
56
56
|
if (!meta) {
|
|
57
57
|
meta = { hash, path: relativePath, links: [] };
|
|
58
58
|
metadata_1.MetaData.files.push(meta);
|
|
59
59
|
}
|
|
60
|
+
else {
|
|
61
|
+
meta.hash = hash;
|
|
62
|
+
}
|
|
60
63
|
// 处理 Markdown 文件
|
|
61
64
|
if (fullPath.endsWith('.md')) {
|
|
62
65
|
const content = contentBuffer.toString('utf-8');
|
|
@@ -76,7 +79,7 @@ async function scanSourceFiles() {
|
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
// 移除不再存在的文件元数据
|
|
79
|
-
metadata_1.MetaData.files = metadata_1.MetaData.files.filter(f =>
|
|
82
|
+
metadata_1.MetaData.files = metadata_1.MetaData.files.filter(f => paths.has(f.path));
|
|
80
83
|
// 按路径降序排序 (通常外层目录优先)
|
|
81
84
|
metadata_1.MetaData.files.sort((a, b) =>
|
|
82
85
|
// 第一级按目录排序
|
package/dist/process/template.js
CHANGED
|
@@ -56,17 +56,17 @@ const spiderStaticSiteGenerator = async () => {
|
|
|
56
56
|
}
|
|
57
57
|
const isVisited = new Set();
|
|
58
58
|
const contents = [];
|
|
59
|
-
// 预加载所有 Markdown
|
|
59
|
+
// 预加载所有 Markdown 内容,因为 React 内部异步渲染比较麻烦
|
|
60
60
|
for (const file of metadata_1.MetaData.files) {
|
|
61
61
|
if (!file.path.endsWith('.md'))
|
|
62
62
|
continue;
|
|
63
63
|
for (const lang of metadata_1.MetaData.options.langs || []) {
|
|
64
|
-
const markdown = await fs.readFile(path.join(paths_1.CZON_SRC_DIR, lang, file.
|
|
64
|
+
const markdown = await fs.readFile(path.join(paths_1.CZON_SRC_DIR, lang, file.path), 'utf-8');
|
|
65
65
|
const { frontmatter, body } = (0, frontmatter_1.parseFrontmatter)(markdown);
|
|
66
|
-
const markdownHtml = (0, convertMarkdownToHtml_1.convertMarkdownToHtml)(body);
|
|
66
|
+
const markdownHtml = (0, convertMarkdownToHtml_1.convertMarkdownToHtml)(file.path, lang, body);
|
|
67
67
|
contents.push({
|
|
68
68
|
lang,
|
|
69
|
-
|
|
69
|
+
file,
|
|
70
70
|
body: markdownHtml,
|
|
71
71
|
frontmatter,
|
|
72
72
|
});
|
|
@@ -83,42 +83,6 @@ const spiderStaticSiteGenerator = async () => {
|
|
|
83
83
|
site: metadata_1.MetaData,
|
|
84
84
|
contents,
|
|
85
85
|
});
|
|
86
|
-
// 内部链接: czon://hash 格式的链接替换为 /{lang}/{slug}.html
|
|
87
|
-
html = html.replace(/href="([^"]+)"/g, (match, link) => {
|
|
88
|
-
console.info(`🕷️ Processing link: ${link} in path: ${currentPath}`);
|
|
89
|
-
if (link.startsWith('czon://')) {
|
|
90
|
-
const hash = link.replace('czon://', '');
|
|
91
|
-
console.info(` 🔗 Replacing internal link for hash: ${hash}`);
|
|
92
|
-
const file = metadata_1.MetaData.files.find(f => f.hash === hash);
|
|
93
|
-
if (!file || !file.metadata) {
|
|
94
|
-
console.warn(`⚠️ Link target not found for hash ${hash} in path ${currentPath}`);
|
|
95
|
-
return match;
|
|
96
|
-
}
|
|
97
|
-
const slug = file.metadata.slug;
|
|
98
|
-
const targetPath = path.resolve('/', path.dirname(currentPath), `${slug}.html`);
|
|
99
|
-
const href = path.relative(path.dirname(currentPath), targetPath);
|
|
100
|
-
return `href="${href}"`;
|
|
101
|
-
}
|
|
102
|
-
return match;
|
|
103
|
-
});
|
|
104
|
-
// 替换 src 中的 czon://hash 链接
|
|
105
|
-
html = html.replace(/src="([^"]+)"/g, (match, link) => {
|
|
106
|
-
console.info(`🕷️ Processing src link: ${link} in path: ${currentPath}`);
|
|
107
|
-
if (link.startsWith('czon://')) {
|
|
108
|
-
const hash = link.replace('czon://', '');
|
|
109
|
-
console.info(` 🔗 Replacing internal src link for hash: ${hash}`);
|
|
110
|
-
const file = metadata_1.MetaData.files.find(f => f.hash === hash);
|
|
111
|
-
if (!file) {
|
|
112
|
-
console.warn(`⚠️ Src link target not found for hash ${hash} in path ${currentPath}`);
|
|
113
|
-
return match;
|
|
114
|
-
}
|
|
115
|
-
const ext = path.extname(file.path);
|
|
116
|
-
const targetPath = path.join(paths_1.CZON_DIST_RAW_CONTENT_DIR, file.hash + ext);
|
|
117
|
-
const href = path.relative(path.join(paths_1.CZON_DIST_DIR, path.dirname(currentPath)), targetPath);
|
|
118
|
-
return `src="${href}"`;
|
|
119
|
-
}
|
|
120
|
-
return match;
|
|
121
|
-
});
|
|
122
86
|
console.info(`🕷️ Crawled ${currentPath}`);
|
|
123
87
|
// 收集 URL 用于 sitemap
|
|
124
88
|
const urlMatch = currentPath.match(/^\/([^/]+)\/(.+)\.html$/);
|
|
@@ -143,7 +107,11 @@ const spiderStaticSiteGenerator = async () => {
|
|
|
143
107
|
const link = match[1];
|
|
144
108
|
if (URL.canParse(link))
|
|
145
109
|
continue; // 跳过绝对 URL
|
|
110
|
+
if (link.startsWith('#'))
|
|
111
|
+
continue; // 跳过页面内锚点链接
|
|
146
112
|
const resolvedPath = path.resolve('/', path.dirname(currentPath), link);
|
|
113
|
+
if (resolvedPath.startsWith('/__raw__/'))
|
|
114
|
+
continue; // 跳过原始内容目录
|
|
147
115
|
console.info(` ➕ Found link: ${link} -> ${resolvedPath} (${isVisited.has(resolvedPath) ? 'visited' : 'new'})`);
|
|
148
116
|
if (!isVisited.has(resolvedPath)) {
|
|
149
117
|
queue.push(resolvedPath);
|
|
@@ -1,86 +1,125 @@
|
|
|
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
|
+
options?.signal?.addEventListener('abort', () => {
|
|
74
|
+
console.info(`🛑 Aborting OpenCode session ${session.data.id}...`);
|
|
75
|
+
client.session.abort({
|
|
76
|
+
path: {
|
|
77
|
+
id: session.data.id,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
const directoryBase64 = Buffer.from(session.data.directory).toString('base64');
|
|
82
|
+
const url = `${baseUrl}/${directoryBase64}/session/${session.data.id}`;
|
|
83
|
+
console.info('OpenCode Session Created', url);
|
|
84
|
+
const response = await client.session.prompt({
|
|
85
|
+
path: {
|
|
86
|
+
id: session.data.id,
|
|
87
|
+
},
|
|
88
|
+
body: {
|
|
89
|
+
model: modelObj,
|
|
90
|
+
agent,
|
|
91
|
+
parts: [
|
|
92
|
+
{
|
|
93
|
+
type: 'text',
|
|
94
|
+
text: prompt,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
query: {
|
|
99
|
+
directory: cwd,
|
|
100
|
+
},
|
|
101
|
+
signal,
|
|
102
|
+
});
|
|
103
|
+
if (cancelled) {
|
|
104
|
+
throw new Error('Cancelled');
|
|
105
|
+
}
|
|
106
|
+
if (response.error) {
|
|
107
|
+
throw new Error(`OpenCode API error: ${JSON.stringify(response.error)}`);
|
|
108
|
+
}
|
|
109
|
+
// await client.session.delete({
|
|
110
|
+
// path: {
|
|
111
|
+
// id: session.data.id,
|
|
112
|
+
// },
|
|
113
|
+
// });
|
|
114
|
+
cleanup();
|
|
115
|
+
resolve();
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
if (cancelled) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
cleanup();
|
|
122
|
+
reject(new Error(`OpenCode SDK error: ${err instanceof Error ? err.message : String(err)}. Make sure an OpenCode server is running.`));
|
|
84
123
|
}
|
|
85
124
|
});
|
|
86
125
|
};
|
package/dist/ssg/ContentPage.js
CHANGED
|
@@ -21,7 +21,7 @@ const ContentPage = props => {
|
|
|
21
21
|
const date = frontmatter.date || '--';
|
|
22
22
|
const tags = frontmatter.tags || [];
|
|
23
23
|
const category = props.file.category;
|
|
24
|
-
const relatedContents = props.ctx.site.files.filter(f => f.category === category && f.
|
|
24
|
+
const relatedContents = props.ctx.site.files.filter(f => f.category === category && f.path !== props.file.path);
|
|
25
25
|
// 查找指向当前文章的其他文章
|
|
26
26
|
const thisPath = (0, node_path_1.resolve)('/', props.file.path);
|
|
27
27
|
const referencedFiles = props.ctx.site.files.filter(f => f.links.some(link => (0, node_path_1.resolve)('/', (0, node_path_1.dirname)(f.path), link) === thisPath));
|
|
@@ -56,15 +56,15 @@ const ContentPage = props => {
|
|
|
56
56
|
relatedContents.length > 0 && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
57
57
|
react_1.default.createElement("h2", null, "See Also"),
|
|
58
58
|
react_1.default.createElement("ul", null, relatedContents.map(f => {
|
|
59
|
-
const theContent = props.ctx.contents.find(c => c.lang === props.lang && c.
|
|
60
|
-
return (react_1.default.createElement("li", { key: f.
|
|
59
|
+
const theContent = props.ctx.contents.find(c => c.lang === props.lang && c.file === f);
|
|
60
|
+
return (react_1.default.createElement("li", { key: f.path },
|
|
61
61
|
react_1.default.createElement("a", { href: `${f.metadata?.slug}.html` }, theContent?.frontmatter.title)));
|
|
62
62
|
})))),
|
|
63
63
|
referencedFiles.length > 0 && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
64
64
|
react_1.default.createElement("h2", null, "Referenced By"),
|
|
65
65
|
react_1.default.createElement("ul", null, referencedFiles.map(f => {
|
|
66
|
-
const theContent = props.ctx.contents.find(c => c.lang === props.lang && c.
|
|
67
|
-
return (react_1.default.createElement("li", { key: f.
|
|
66
|
+
const theContent = props.ctx.contents.find(c => c.lang === props.lang && c.file === f);
|
|
67
|
+
return (react_1.default.createElement("li", { key: f.path },
|
|
68
68
|
react_1.default.createElement("a", { href: `${f.metadata?.slug}.html` }, theContent?.frontmatter.title)));
|
|
69
69
|
}))))),
|
|
70
70
|
react_1.default.createElement("footer", { className: "footer" },
|
package/dist/ssg/IndexPage.js
CHANGED
|
@@ -64,8 +64,7 @@ const IndexPage = props => {
|
|
|
64
64
|
")"))));
|
|
65
65
|
}))),
|
|
66
66
|
react_1.default.createElement("div", null, contents.map(file => {
|
|
67
|
-
|
|
68
|
-
return (react_1.default.createElement("div", { className: "mb-6", key: file.hash },
|
|
67
|
+
return (react_1.default.createElement("div", { className: "mb-6", key: file.path },
|
|
69
68
|
react_1.default.createElement(ContentMeta_1.ContentMeta, { ctx: props.ctx, file: file, lang: props.lang })));
|
|
70
69
|
})),
|
|
71
70
|
react_1.default.createElement("footer", null,
|
package/dist/ssg/app.js
CHANGED
|
@@ -28,7 +28,7 @@ const App = (props) => {
|
|
|
28
28
|
// 渲染文章页面
|
|
29
29
|
for (const file of props.site.files) {
|
|
30
30
|
if (props.path === `/${lang}/${file.metadata?.slug}.html`) {
|
|
31
|
-
const theContent = props.contents.find(c => c.lang === lang && c.
|
|
31
|
+
const theContent = props.contents.find(c => c.lang === lang && c.file === file);
|
|
32
32
|
if (!theContent)
|
|
33
33
|
return react_1.default.createElement(RedirectPage_1.RedirectPage, { from: props.path, to: `/index.html` });
|
|
34
34
|
return react_1.default.createElement(ContentPage_1.ContentPage, { ctx: props, file: file, lang: lang, content: theContent });
|
|
@@ -7,7 +7,7 @@ exports.ContentMeta = void 0;
|
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
8
|
const TagList_1 = require("./TagList");
|
|
9
9
|
const ContentMeta = props => {
|
|
10
|
-
const content = props.ctx.contents.find(c => c.
|
|
10
|
+
const content = props.ctx.contents.find(c => c.file === props.file && c.lang === props.lang);
|
|
11
11
|
const frontmatter = content?.frontmatter || {};
|
|
12
12
|
const title = frontmatter.title;
|
|
13
13
|
const summary = frontmatter.summary;
|
|
@@ -22,10 +22,10 @@ const Navigator = props => {
|
|
|
22
22
|
react_1.default.createElement("li", { className: "nav-item font-bold", key: categoryKey }, category || '--'),
|
|
23
23
|
filesInCategory.map(file => {
|
|
24
24
|
const link = file.metadata.slug + '.html';
|
|
25
|
-
const isActive = props.file
|
|
26
|
-
const theContent = props.ctx.contents.find(c => c.lang === props.lang && c.
|
|
25
|
+
const isActive = props.file === file;
|
|
26
|
+
const theContent = props.ctx.contents.find(c => c.lang === props.lang && c.file === file);
|
|
27
27
|
const theTitle = theContent?.frontmatter?.title || file.metadata.title || '(no title)';
|
|
28
|
-
return (react_1.default.createElement("li", { className: "nav-item", key: file.
|
|
28
|
+
return (react_1.default.createElement("li", { className: "nav-item", key: file.path },
|
|
29
29
|
react_1.default.createElement("a", { href: link, className: `nav-link ${isActive ? 'active' : ''}` },
|
|
30
30
|
theTitle,
|
|
31
31
|
" ",
|
|
@@ -8,6 +8,8 @@ const highlight_js_1 = __importDefault(require("highlight.js"));
|
|
|
8
8
|
const marked_1 = require("marked");
|
|
9
9
|
const marked_katex_extension_1 = __importDefault(require("marked-katex-extension"));
|
|
10
10
|
const marked_footnote_1 = __importDefault(require("marked-footnote"));
|
|
11
|
+
const metadata_1 = require("../metadata");
|
|
12
|
+
const path_1 = require("path");
|
|
11
13
|
// 辅助函数:转义 HTML 特殊字符
|
|
12
14
|
function escapeHtml(unsafe) {
|
|
13
15
|
return unsafe
|
|
@@ -24,10 +26,67 @@ marked_1.marked.use((0, marked_footnote_1.default)());
|
|
|
24
26
|
* @param mdContent Markdown 内容字符串
|
|
25
27
|
* @returns 转换后的 HTML 字符串
|
|
26
28
|
*/
|
|
27
|
-
const convertMarkdownToHtml = (mdContent) => {
|
|
29
|
+
const convertMarkdownToHtml = (path, lang, mdContent) => {
|
|
30
|
+
const sourceFileMeta = metadata_1.MetaData.files.find(f => f.path === path);
|
|
28
31
|
// 创建自定义渲染器
|
|
29
32
|
const renderer = new marked_1.marked.Renderer();
|
|
30
33
|
const originalCodeRenderer = renderer.code;
|
|
34
|
+
const originalLinkRenderer = renderer.link;
|
|
35
|
+
renderer.link = function (link) {
|
|
36
|
+
console.info(`🔗 #### Processing link in Markdown: ${link.href} in file: ${path}`);
|
|
37
|
+
if (URL.canParse(link.href)) {
|
|
38
|
+
// 保持原有链接渲染行为
|
|
39
|
+
return originalLinkRenderer.call(this, link);
|
|
40
|
+
}
|
|
41
|
+
if (!sourceFileMeta?.metadata?.slug) {
|
|
42
|
+
console.warn(`⚠️ Source file metadata slug not found for path ${path}`);
|
|
43
|
+
return originalLinkRenderer.call(this, link);
|
|
44
|
+
}
|
|
45
|
+
const sourceFileHtmlPath = (0, path_1.resolve)('/', lang, `${sourceFileMeta.metadata.slug}.html`);
|
|
46
|
+
const resolvedPath = (0, path_1.join)((0, path_1.dirname)(path), link.href);
|
|
47
|
+
const file = metadata_1.MetaData.files.find(f => f.path === resolvedPath);
|
|
48
|
+
if (!file) {
|
|
49
|
+
console.warn(`⚠️ Link target not found for path ${resolvedPath} in file ${path}`);
|
|
50
|
+
return originalLinkRenderer.call(this, link);
|
|
51
|
+
}
|
|
52
|
+
if (link.href.endsWith('.md')) {
|
|
53
|
+
if (!file.metadata?.slug) {
|
|
54
|
+
console.warn(`⚠️ Missing slug metadata for file ${file.path}`);
|
|
55
|
+
return originalLinkRenderer.call(this, link);
|
|
56
|
+
}
|
|
57
|
+
const slug = file.metadata.slug;
|
|
58
|
+
const targetPath = (0, path_1.resolve)('/', lang, `${slug}.html`);
|
|
59
|
+
// 将 .md 链接转换为对应的 HTML 文件链接
|
|
60
|
+
const href = (0, path_1.relative)((0, path_1.dirname)(sourceFileHtmlPath), targetPath);
|
|
61
|
+
const modifiedLink = {
|
|
62
|
+
...link,
|
|
63
|
+
href,
|
|
64
|
+
};
|
|
65
|
+
return originalLinkRenderer.call(this, modifiedLink);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.info(` 🔗 #### Processing resource link in Markdown: ${link.href} in file: ${path}`);
|
|
69
|
+
// 其他资源链接到 __raw__ 目录
|
|
70
|
+
const resourcePath = (0, path_1.resolve)('/', (0, path_1.dirname)(path), link.href);
|
|
71
|
+
const href = (0, path_1.relative)((0, path_1.dirname)(sourceFileHtmlPath), (0, path_1.join)('/', '__raw__', resourcePath));
|
|
72
|
+
console.info(` ➡️ Converted resource link to: ${href}`);
|
|
73
|
+
const modifiedLink = {
|
|
74
|
+
...link,
|
|
75
|
+
href: href,
|
|
76
|
+
};
|
|
77
|
+
return originalLinkRenderer.call(this, modifiedLink);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const originalImageRenderer = renderer.image;
|
|
81
|
+
renderer.image = function (image) {
|
|
82
|
+
console.info(`🖼️ #### Processing image in Markdown: ${image.href} in file: ${path}`);
|
|
83
|
+
if (URL.canParse(image.href)) {
|
|
84
|
+
// 保持原有图片渲染行为
|
|
85
|
+
return originalImageRenderer.call(this, image);
|
|
86
|
+
}
|
|
87
|
+
const imagePath = (0, path_1.join)((0, path_1.dirname)(path), image.href);
|
|
88
|
+
return `<img src="${(0, path_1.join)('..', '__raw__', imagePath)}" alt="${image.text}" />`;
|
|
89
|
+
};
|
|
31
90
|
// 重写代码块渲染器以支持 Mermaid - 使用 any 类型绕过类型检查
|
|
32
91
|
renderer.code = function (code, language, isEscaped) {
|
|
33
92
|
// 在 marked 17+ 中,code 参数是一个对象,包含 text 和 lang 属性
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "czon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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