chore: establish YCode replication baseline and TODO

This commit is contained in:
xiaohan.yuan committed 2026-10-17 02:43:49 +08:00
commit 6dc59f89ba
1118 files changed
+196002

No files matched your search

+196
View File
@@ -0,0 +1,196 @@
/**
* 从 icons.json 生成 src/renderer/src/components/icons.tsx
*
* 用法:node scripts/gen-icons.mjs <iconsJson> <outTsx>
*/
import { readFile, writeFile } from 'node:fs/promises'
const src = process.argv[2] ?? 'C:/Users/Administrator/.ycode/captures/icons.json'
const out = process.argv[3] ?? 'src/renderer/src/components/icons.tsx'
/** 第二轮采集的导航/账号图标(可选) */
const navSource = process.argv[4] ?? 'C:/Users/Administrator/.ycode/captures/nav-icons.json'
/** 第三轮:标题栏左上操作区图标(可选) */
const topbarSource = process.argv[5] ?? 'C:/Users/Administrator/.ycode/captures/topbar-icons.json'
/** 图标名 → [target, svgIndex, 覆盖的渲染尺寸] */
const MAP = {
IconNewTask: ['newTask', 0, 16],
IconChevronDown: ['templates', 1, 14],
IconFilter: ['topbarFilter', 0, 16],
IconCompact: ['sectionCompact', 0, 16],
IconSectionAdd: ['sectionAdd', 0, 16],
IconPlus: ['inputToolbarLeft', 0, 16],
IconShield: ['inputToolbarLeft', 1, 16],
IconChevronSmall: ['inputToolbarRight', 2, 12],
IconMic: ['inputToolbarRight', 3, 16],
IconStop: ['sendButton', 0, 32],
IconTab: ['tabIcon', 0, 13],
IconClose: ['tabClose', 0, 14],
IconChevronRight: ['artifactActions', 0, 8],
IconPanelCollapse: ['panelActions', 0, 16],
IconPanelRight: ['panelActions', 1, 16]
}
/**
* 第二轮:从 nav-icons.json 采集的导航与账号区图标。
* 键为采集时的标签文案 / 选择器。
*/
const NAV_MAP = {
IconNavAssistant: '助理Beta',
IconNavExperts: '专家·技能·连接器',
IconNavScheduled: '定时任务',
IconNavLibrary: '资料库',
IconNavMore: '更多',
IconBell: '.msg-center-bell-btn',
IconMiniprogram: '.user-menu-trigger-miniprogram',
IconDiscover: '.industry-template-switcher__host button'
}
const NAV_SIZES = {
IconNavAssistant: 16,
IconNavExperts: 16,
IconNavScheduled: 16,
IconNavLibrary: 16,
IconNavMore: 16,
IconBell: 16,
IconMiniprogram: 16,
IconDiscover: 14
}
/** HTML 连字符属性 → JSX 驼峰属性 */
const JSX_ATTRS = {
'fill-rule': 'fillRule',
'clip-rule': 'clipRule',
'stroke-width': 'strokeWidth',
'stroke-linecap': 'strokeLinecap',
'stroke-linejoin': 'strokeLinejoin',
'stroke-dasharray': 'strokeDasharray',
'stroke-dashoffset': 'strokeDashoffset',
'stroke-miterlimit': 'strokeMiterlimit',
'clip-path': 'clipPath',
'fill-opacity': 'fillOpacity',
'stroke-opacity': 'strokeOpacity'
}
/** 只保留几何与绘制属性,去掉 class / aria 等宿主细节 */
function sanitize(svg, size) {
let out = svg
out = out.replace(/\sclass="[^"]*"/g, '')
out = out.replace(/\sstyle="[^"]*"/g, '')
out = out.replace(/\saria-hidden="[^"]*"/g, '')
out = out.replace(/\sxmlns="[^"]*"/g, '')
// 统一由外层控制尺寸
out = out.replace(/\swidth="[^"]*"/, '')
out = out.replace(/\sheight="[^"]*"/, '')
for (const [html, jsx] of Object.entries(JSX_ATTRS)) {
out = out.replace(new RegExp(`\\s${html}=`, 'g'), ` ${jsx}=`)
}
out = out.replace(/<svg /, `<svg width="__SIZE__" height="__SIZE__" `)
if (size) out = out.replace(/__SIZE__/g, String(size))
return out
}
const data = JSON.parse(await readFile(src, 'utf8'))
const parts = [
'/**',
' * 图标组件。',
' *',
' * ⚠️ 来源说明:本文件中的 SVG 路径取自本机安装的 WorkBuddy 运行时界面(CDP 采集,',
' * 见 scripts/wb-icons.mjs + scripts/gen-icons.mjs),用于当前阶段的 1:1 视觉还原。',
' * 这些矢量路径属于第三方产品的界面资产,**仅作临时占位**;',
' * 正式发布前必须替换为自有图标或开源图标集(如 Lucide / VS Code codicons,后者为 MIT)。',
' *',
' * 重新生成:node scripts/gen-icons.mjs <icons.json> <输出路径>',
' */',
'',
"import type { SVGProps } from 'react'",
'',
'type IconProps = SVGProps<SVGSVGElement>',
''
]
for (const [name, [target, index, size]] of Object.entries(MAP)) {
const entry = data[target]
const svg = entry?.svgs?.[index]
if (!svg) {
parts.push(`// ${name}: 采集数据缺失(${target}[${index}])`)
parts.push('')
continue
}
const cleaned = sanitize(svg, size)
.replace(/\sfill="currentColor"/g, ' fill="currentColor"')
.replace(/^<svg /, `<svg {...props} `)
parts.push(`export function ${name}(props: IconProps) {`)
parts.push(' return (')
parts.push(' ' + cleaned)
parts.push(' )')
parts.push('}')
parts.push('')
}
/* ---- 第二轮:导航与账号区图标 ---- */
let navData = null
if (navSource) {
try {
navData = JSON.parse(await readFile(navSource, 'utf8'))
} catch {
navData = null
}
}
for (const [name, key] of Object.entries(NAV_MAP)) {
const svg = navData?.[key]
if (!svg) {
parts.push(`// ${name}: 导航采集数据缺失(${key})`)
parts.push('')
continue
}
const cleaned = sanitize(svg, NAV_SIZES[name])
.replace(/\sfill="currentColor"/g, ' fill="currentColor"')
.replace(/^<svg /, `<svg {...props} `)
parts.push(`export function ${name}(props: IconProps) {`)
parts.push(' return (')
parts.push(' ' + cleaned)
parts.push(' )')
parts.push('}')
parts.push('')
}
/* ---- 第三轮:标题栏左上操作区(收起侧边栏 / 搜索 / 筛选)---- */
const TOPBAR_MAP = {
IconCollapseSidebar: '收起侧边栏',
IconSearchTopbar: '搜索'
}
let topbarData = null
if (topbarSource) {
try {
topbarData = JSON.parse(await readFile(topbarSource, 'utf8'))
} catch {
topbarData = null
}
}
for (const [name, title] of Object.entries(TOPBAR_MAP)) {
const entry = Array.isArray(topbarData) ? topbarData.find((n) => n.title === title) : null
if (!entry?.svg) {
parts.push(`// ${name}: 标题栏采集数据缺失(${title})`)
parts.push('')
continue
}
const cleaned = sanitize(entry.svg, 16)
.replace(/\sfill="currentColor"/g, ' fill="currentColor"')
.replace(/^<svg /, `<svg {...props} `)
parts.push(`export function ${name}(props: IconProps) {`)
parts.push(' return (')
parts.push(' ' + cleaned)
parts.push(' )')
parts.push('}')
parts.push('')
}
await writeFile(out, parts.join('\n'), 'utf8')
console.log('已生成', out)
console.log('图标数:', Object.keys(MAP).length)