Files
ant-design/scripts/generate-llms-semantic.ts
二货爱吃白萝卜 f842163a70 docs: Base AI generate theme (#54412)
* chore: init link

* docs: init

* chore: adjust logic

* chore: theme connect

* chore: add prompt talk

* chore: support chat prompt

* chore: api call

* chore: sse

* feat: site alg

* chore: init layer

* chore: fix order

* chore: update ts def

* chore: tmp of it

* chore: init script

* chore: add generate script

* chore: fix script

* chore: llms semantic

* chore: update desc

* chore: fix lint

* chore: fix lint
2025-07-18 16:19:51 +08:00

226 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Note: Generated By Copilot
import path from 'path';
import fs from 'fs-extra';
import { glob } from 'glob';
// 特殊组件名转换映射
const ConvertMap: Record<string, string> = {
'badge:ribbon': 'ribbon',
'floatButton:group': 'floatButtonGroup',
'input:input': 'input',
'input:otp': 'otp',
'input:search': 'inputSearch',
'input:textarea': 'textArea',
};
// 将 kebab-case 转换为 camelCase
function toCamelCase(str: string): string {
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}
// 构建嵌套结构的辅助函数
function buildNestedStructure(flatSemantics: Record<string, string>): any {
const result: any = {};
// 首先,收集所有带点号的键,确定哪些是父级
const nestedKeys = new Set<string>();
for (const key of Object.keys(flatSemantics)) {
if (key.includes('.')) {
const parentKey = key.split('.')[0];
nestedKeys.add(parentKey);
}
}
for (const [key, value] of Object.entries(flatSemantics)) {
if (key.includes('.')) {
const parts = key.split('.');
let current = result;
// 遍历除最后一个部分外的所有部分
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (!current[part]) {
current[part] = {};
} else if (typeof current[part] === 'string') {
// 如果已经存在一个字符串值,需要将其转换为对象
// 保留原有的值作为一个特殊的 _value 属性或者忽略冲突
// console.warn(
// `Warning: Key conflict for '${part}', nested structure will override simple value`,
// );
current[part] = {};
}
current = current[part];
}
// 设置最后一个部分的值
current[parts[parts.length - 1]] = value;
} else {
// 只有当这个键不是某个嵌套结构的父级时,才直接设置值
if (!nestedKeys.has(key)) {
result[key] = value;
} else {
// 如果这个键将成为父级,先确保它是一个对象
if (!result[key]) {
result[key] = {};
}
}
}
}
return result;
}
// 递归生成 markdown 格式的嵌套结构
function generateMarkdownStructure(obj: any, prefix = '', indent = 0): string {
let result = '';
for (const [key, value] of Object.entries(obj)) {
const indentStr = ' '.repeat(indent);
if (typeof value === 'string') {
result += `${indentStr}- \`${key}\`: ${value}\n`;
} else {
result += `${indentStr}- \`${key}\`:\n`;
result += generateMarkdownStructure(value, prefix, indent + 1);
}
}
return result;
}
// 递归打印嵌套结构的辅助函数(已注释掉)
// function printNestedStructure(obj: any, prefix = '', indent = 0): void {
// for (const [key, value] of Object.entries(obj)) {
// const indentStr = ' '.repeat(indent);
// if (typeof value === 'string') {
// console.log(`${indentStr}- ${key}: ${value}`);
// } else {
// console.log(`${indentStr}- ${key}:`);
// printNestedStructure(value, prefix, indent + 1);
// }
// }
// }
async function generateSemanticDesc() {
const cwd = process.cwd();
const siteDir = path.resolve(cwd, '_site');
const docsDir = ['components', 'docs'];
// Ensure siteDir
await fs.ensureDir(siteDir);
const docs = await glob(`{${docsDir.join(',')}}/**/demo/_semantic*.tsx`);
// Read `docs` file and generate semantic description.e.g.
// components/float-button/demo/_semantic_group.tsx is to:
// - root: 根元素
// - list: 列表元素
// - item: 列表项元素
// - root: 列表项根元素
// - icon: 列表项图标元素
// - content: 列表项内容元素
// - trigger:
// - root: 触发元素
// - icon: 触发图标元素
// - content: 触发内容元素
const semanticDescriptions: Record<string, any> = {};
for (const docPath of docs) {
try {
// 读取文件内容
const content = await fs.readFile(docPath, 'utf-8');
// 提取组件名称(从路径中获取)
const componentName = path.basename(path.dirname(path.dirname(docPath)));
const fileName = path.basename(docPath, '.tsx');
// 转换为 camelCase
const camelCaseComponentName = toCamelCase(componentName);
// 如果是 _semantic.tsx使用组件名如果是其他变体添加后缀
let semanticKey = camelCaseComponentName;
if (fileName !== '_semantic') {
const variant = fileName.replace('_semantic_', '').replace('_semantic', '');
if (variant) {
semanticKey = `${camelCaseComponentName}:${variant}`;
}
}
// 检查是否有特殊转换映射
if (ConvertMap[semanticKey]) {
semanticKey = ConvertMap[semanticKey];
}
// 使用正则表达式提取 locales 对象
const localesMatch = content.match(/const locales = \{([\s\S]*?)\};/);
if (!localesMatch) {
// console.warn(`No locales found in ${docPath}`);
continue;
}
// 提取中文 locales
const cnMatch = content.match(/cn:\s*\{([\s\S]*?)\},?\s*en:/);
if (!cnMatch) {
// console.warn(`No Chinese locales found in ${docPath}`);
continue;
}
const cnContent = cnMatch[1];
const flatSemantics: Record<string, string> = {};
// 提取每个语义描述
const semanticMatches = cnContent.matchAll(/['"]?([^'":\s]+)['"]?\s*:\s*['"]([^'"]+)['"],?/g);
for (const match of semanticMatches) {
const [, key, value] = match;
flatSemantics[key] = value;
}
if (Object.keys(flatSemantics).length > 0) {
// 将扁平的语义描述转换为层级结构
const nestedSemantics = buildNestedStructure(flatSemantics);
semanticDescriptions[semanticKey] = nestedSemantics;
// 生成层级结构的描述(已注释掉)
// console.log(`\n${semanticKey}:`);
// printNestedStructure(nestedSemantics);
}
} catch (error) {
console.error(`Error processing ${docPath}:`, error);
}
}
// 生成 markdown 内容
let markdownContent = '# Ant Design 组件语义化描述\n\n';
markdownContent += '本文档包含了 Ant Design 组件库中所有组件的语义化描述信息。\n\n';
markdownContent += `> 总计 ${Object.keys(semanticDescriptions).length} 个组件包含语义化描述\n\n`;
markdownContent += '## 组件列表\n\n';
// 按组件名排序
const sortedComponents = Object.keys(semanticDescriptions).sort();
for (const componentName of sortedComponents) {
const semantics = semanticDescriptions[componentName];
markdownContent += `### ${componentName}\n\n`;
markdownContent += generateMarkdownStructure(semantics);
markdownContent += '\n';
}
// 生成总结(已注释掉)
// console.log('\n=== Semantic Descriptions Summary ===');
// console.log(
// `Total components with semantic descriptions: ${Object.keys(semanticDescriptions).length}`,
// );
// 将结果写入 markdown 文件
const outputPath = path.join(siteDir, 'llms-semantic.md');
await fs.writeFile(outputPath, markdownContent, 'utf-8');
console.log(`Semantic descriptions saved to: ${outputPath}`);
}
(async () => {
if (require.main === module) {
await generateSemanticDesc();
}
})().catch((e) => {
console.error(e);
process.exit(1);
});