Files
ycode/scripts/rea/recover-select-reference.mjs
T

149 lines
16 KiB
JavaScript

// Recover only pure UI definitions from pinned REA exports. Never initialize an original module.
import fs from 'node:fs/promises'
import { createHash } from 'node:crypto'
import { parse } from '@babel/parser'
import postcss from 'postcss'
const out = 'src/renderer/src/components/source-select'
await fs.mkdir(`${out}/provider-assets`, { recursive: true })
const evidence = JSON.parse(await fs.readFile('.tmp/rea-runtime-scripts.json', 'utf8'))
const panel = JSON.parse(await fs.readFile('.tmp/rea-assessment-model-source.json', 'utf8'))
const cache = new Map()
function source(file) {
if (cache.has(file)) return cache.get(file)
const artifact = evidence.items.find(item => item.file_path.endsWith(file))?.source?.artifact
if (!artifact?.text || createHash('sha256').update(artifact.text).digest('hex') !== artifact.sha256) throw Error(`Invalid REA artifact ${file}`)
const ast = parse(artifact.text, { sourceType: 'module' }), definitions = new Map(), exports = new Map(), imports = new Map()
function visit(node) {
if (!node || typeof node !== 'object') return
if (node.type === 'FunctionDeclaration') definitions.set(node.id.name, node)
if (node.type === 'AssignmentExpression' && node.left.type === 'Identifier') definitions.set(node.left.name, node.right)
if (node.type === 'VariableDeclarator' && node.id.type === 'Identifier' && node.init) definitions.set(node.id.name, node.init)
if (node.type === 'ExportNamedDeclaration') for (const entry of node.specifiers) exports.set(entry.exported.name, entry.local.name)
if (node.type === 'ImportDeclaration') for (const entry of node.specifiers) if (entry.imported) imports.set(entry.local.name, { file: node.source.value.replace(/^\.\//, ''), exported: entry.imported.name })
for (const value of Object.values(node)) if (Array.isArray(value)) value.forEach(visit); else if (value && typeof value === 'object') visit(value)
}
visit(ast)
const result = { artifact, definitions, exports, imports }; cache.set(file, result); return result
}
const shared = source('src-DPDj7_qD.js'), contexts = source('contexts-BMeWSUHk.js')
if (shared.artifact.sha256 !== '747e41d215375e1214c366213aea7e077d85421923029c61cbb6e9562be58de0' || contexts.artifact.sha256 !== '5f35b7489b107e21ef452c8ee88a95265756f6fc444841eef869228bbc44768b') throw Error('Pinned source changed')
const reactNames = 'jsx|jsxs|forwardRef|useRef|useState|useMemo|useEffect|useLayoutEffect|useCallback|useId|useImperativeHandle|useContext|cloneElement|isValidElement'
const convert = text => text.replace(new RegExp(`\\(0,[A-Za-z0-9_$]+\\.(${reactNames})\\)`, 'g'), ' $1')
.replace(new RegExp(`\\b[A-Za-z0-9_$]+\\.(${reactNames})\\b`, 'g'), '$1').replace(/\b[A-Za-z0-9_$]+\.Fragment\b/g, 'React.Fragment')
const manifest = { sourceVersion: '5.8.2', rootSha256: panel.normalized_result.root_sha256, scriptEvidenceId: evidence.evidence_id, panelEvidenceId: panel.evidence_id, definitions: [], icons: [], assets: [], css: [] }
const floating = source('floating-ui.react-dom-BDD8Fyfd.js')
if (floating.artifact.sha256 !== '94036e3ed41ddb907b010cedbdb628a57e26e0585e2a9be45c9cbecfdd6ec978') throw Error('Floating source changed')
for (const name of ['getClippingElementAncestors', 'isContainingBlock', 'hasFixedPositionAncestor']) {
const node = floating.definitions.get(name)
manifest.definitions.push({file:'floating-ui.react-dom-BDD8Fyfd.js',sha256:floating.artifact.sha256,name,offset:node.start,length:node.end-node.start,hostAdapter:'SourceFloatingPlatform.ts'})
}
const core = []
for (const name of ['isOptionGroup', 'flattenOptions', 'defaultFilter', 'OptionLabel', 'OptionStringLabel', 'Select', 'pL', 'useControllableInputValue', '_L', 'CheckboxTick', 'bB']) {
const node = shared.definitions.get(name)
if (!node) throw Error(`Missing UI definition ${name}`)
let text = convert(shared.artifact.text.slice(node.start, node.end))
if (name === 'Select') text = text.replace('type:`button`,className:ae', 'type:`button`,"aria-label":r.ariaLabel,className:ae')
core.push(node.type === 'FunctionDeclaration' ? text : `const ${name} = ${text}`)
manifest.definitions.push({ file: 'src-DPDj7_qD.js', sha256: shared.artifact.sha256, name, offset: node.start, length: node.end - node.start })
}
await fs.writeFile(`${out}/RecoveredSelectCore.tsx`, `// @ts-nocheck\n// Pure Select/Input/Checkbox definitions recovered from REA ${evidence.evidence_id}.\nimport * as React from 'react'\nimport {forwardRef,useRef,useState,useMemo,useEffect,useLayoutEffect,useCallback,useId,useImperativeHandle,useContext,createContext} from 'react'\nimport {jsx,jsxs} from 'react/jsx-runtime'\nimport { SourceSelectPopover as Popover, SourceSelectTooltip as Tooltip } from './SourceSelectPopover'\nimport {SelectSearchIcon as L_,SelectClearIcon as GP,SelectCaretIcon as Ys,SelectCheckIcon as Fs,SelectLoadingIcon as Hf} from './SourceSelectIcons'\nconst getA11yLabel=(key)=>({clear:'清除',search:'搜索',remove:'移除'}[key]??key)\nconst gB=createContext(null)\n${core.join('\n')}\nconst ML=_L,EB=bB\nexport const RecoveredSelect=Select\n`)
const tooltip = []
for (const name of ['isPressTransparent', 'isHoverSuppressedByPress', 'isElementTruncated', 'isSubtreeTruncated', 'normalizeVisibleText', 'collectVisibleText', 'hasVisibleTriggerText', 'hasTruncationStyle', 'shouldGateOnTruncation', 'snapToDevicePixel', 'Tooltip']) {
const node = shared.definitions.get(name)
let text = convert(shared.artifact.text.slice(node.start,node.end)).replaceAll('s.ref','s.props.ref')
tooltip.push(node.type === 'FunctionDeclaration' ? text : `const ${name}=${text}`)
manifest.definitions.push({file:'src-DPDj7_qD.js',sha256:shared.artifact.sha256,name,offset:node.start,length:node.end-node.start})
}
await fs.writeFile(`${out}/RecoveredSelectTooltip.tsx`, `// @ts-nocheck\n// Literal source Tooltip and truncation helpers; React 19 ref access and scoped portal are host adapters.\nimport * as React from 'react'\nimport {useState,useRef,useEffect,useMemo,cloneElement,isValidElement} from 'react'\nimport {jsx,jsxs} from 'react/jsx-runtime'\nimport {useFloating,offset as v,flip as _,shift as x,size as g,arrow as S,hide as y,autoUpdate as C,useMergeRefs as ee,useHover as q,safePolygon as te,useFocus as ne,useDismiss as ae,useRole as de,useInteractions as le,useTransitionStatus as G,FloatingPortal} from '@floating-ui/react'\nimport {sourceFloatingPlatform} from './SourceFloatingPlatform'\nconst oe=({layer,...options})=>useFloating({...options,platform:sourceFloatingPlatform})\nconst ce=({children})=>jsx(FloatingPortal,{children:jsx('span',{className:'source-select-host',children})})\nconst uz=.1,dz=new Set(['SVG','IMG','VIDEO','CANVAS','AUDIO']),hz=3,gz=8,_z=12,vz=240,yz=150,bz=100,xz=8,Sz=72,Cz=8,wz=new Map,Tz=new Map,wR=4,TR=1000,ER='data-pointer-press-transparent';let DR=null\n${tooltip.join('\n')}\n// Shipped tooltip press suppression, without executing the original bundle initializer.\nif(typeof document!=='undefined'){document.addEventListener('pointerdown',r=>{DR=isPressTransparent(r.target)?null:{x:r.clientX,y:r.clientY,at:Date.now()}},true);document.addEventListener('pointermove',r=>{if(DR&&(Math.abs(r.clientX-DR.x)>wR||Math.abs(r.clientY-DR.y)>wR))DR=null},true)}\nexport const RecoveredSelectTooltip=Tooltip\n`)
function resolve(file, name) {
const entry = source(file); let node = entry.definitions.get(name)
if (!node && entry.imports.has(name)) { const imported = entry.imports.get(name), target = source(imported.file); return resolve(imported.file, target.exports.get(imported.exported)) }
if (!node) throw Error(`Missing ${file}:${name}`)
for (let count = 0; count < 8; count++) {
if (node.type === 'Identifier') return resolve(file, node.name)
if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'createIcon') {
if (node.arguments[0].type === 'ObjectExpression') return { ...entry, node: node.arguments[0], file, asset: true }
if (node.arguments[0].type !== 'Identifier') throw Error(`Unknown icon ${file}:${name}`)
return resolve(file, node.arguments[0].name)
}
break
}
return { ...entry, node, file }
}
const icons = []
function icon(file, binding, exported) {
const item = resolve(file, binding), text = convert(item.artifact.text.slice(item.node.start, item.node.end))
if (!/\b(?:jsx|jsxs)\(`svg`/.test(text)) throw Error(`Not a pure SVG ${file}:${binding}`)
icons.push(`export const ${exported} = ${item.node.type === 'FunctionDeclaration' ? text.replace(/^function [^(]+/, 'function') : text}`)
manifest.icons.push({ file, sha256: item.artifact.sha256, binding, exported, offset: item.node.start })
}
for (const [binding, exported] of [['L_', 'SelectSearchGlyph'], ['GP', 'SelectClearGlyph'], ['Ys', 'SelectCaretGlyph'], ['Fs', 'SelectCheckGlyph'], ['Hf', 'SelectLoadingGlyph']]) icon('src-DPDj7_qD.js', binding, exported)
await fs.writeFile(`${out}/SourceSelectIcons.tsx`, `// @ts-nocheck\n// Exact SVG paths/transforms from pinned REA ${evidence.evidence_id}.\nimport {jsx,jsxs} from 'react/jsx-runtime'\nimport {forwardRef,useId} from 'react'\n${icons.join('\n')}\nconst wrap=(Glyph)=>(props={})=>{const {size=16,spin,className='',...rest}=props;return jsx(Glyph,{width:size,height:size,...rest,className:['wb-icon',spin?'wb-icon--spin':'',className].filter(Boolean).join(' ')})}\nexport const SelectSearchIcon=wrap(SelectSearchGlyph),SelectClearIcon=wrap(SelectClearGlyph),SelectCaretIcon=wrap(SelectCaretGlyph),SelectCheckIcon=wrap(SelectCheckGlyph),SelectLoadingIcon=wrap(SelectLoadingGlyph)\n`)
// Provider UI imports are resolved from the source panel's icon map, not invented vendor marks.
const vendors = { glm: ['contexts-BMeWSUHk.js', 'tE'], kimi: ['src-DPDj7_qD.js', 'tc'], minimax: ['contexts-BMeWSUHk.js', 'iE'],
deepseek: ['contexts-BMeWSUHk.js', 'QT'], openai: ['contexts-BMeWSUHk.js', 'oE'], gemini: ['contexts-BMeWSUHk.js', 'nE'],
mistral: ['contexts-BMeWSUHk.js', 'aE'], groq: ['contexts-BMeWSUHk.js', 'rE'], 'together-ai': ['contexts-BMeWSUHk.js', 'cE'],
'fireworks-ai': ['contexts-BMeWSUHk.js', 'eE'], perplexity: ['contexts-BMeWSUHk.js', 'sE'], custom: ['src-DPDj7_qD.js', 'ua'] }
const vendorDefinitions = []
for (const [vendor, [file, exportedName]] of Object.entries(vendors)) {
const entry = source(file), binding = entry.exports.get(exportedName)
let item = resolve(file, binding)
// Contexts reexports import bindings; follow only the original static import edge.
if (!item.node) throw Error(`No icon ${vendor}`)
let text = convert(item.artifact.text.slice(item.node.start, item.node.end))
if (item.asset) {
const resource = JSON.parse(await fs.readFile('.tmp/rea-model-select-icons.json', 'utf8'))
if (resource.normalized_result.root_sha256 !== panel.normalized_result.root_sha256 || vendor !== 'kimi') throw Error('Unknown asset icon')
const file = resource.normalized_result.files.find(file => file.path === 'renderer/assets/kimi-DiLyRSiJ.svg')
const bytes = Buffer.from(file.data_base64, 'base64')
if (createHash('sha256').update(bytes).digest('hex') !== file.sha256) throw Error('Icon bytes changed')
await fs.writeFile(`${out}/provider-assets/kimi.svg`, bytes)
vendorDefinitions.push(`import ProviderKimiAsset from './provider-assets/kimi.svg'\nconst ProviderKimiIcon=(props)=>jsx('img',{...props,src:ProviderKimiAsset,alt:'Kimi',draggable:false})`)
manifest.assets.push({ vendor, evidenceId: resource.evidence_id, path: file.path, bytes: bytes.length, sha256: file.sha256 })
continue
}
if (!/\b(?:jsx|jsxs)\(`svg`/.test(text)) throw Error(`Provider ${vendor} needs explicit dependency recovery`)
const name = 'Provider' + vendor.replace(/(^|-)([a-z])/g, (_all, _dash, char) => char.toUpperCase()) + 'Icon'
vendorDefinitions.push(`const ${name} = ${item.node.type === 'FunctionDeclaration' ? text.replace(/^function [^(]+/, 'function') : text}`)
manifest.icons.push({ file: item.file, sha256: item.artifact.sha256, binding, exported: name, vendor, offset: item.node.start })
}
for (const [vendor, binding] of [['tencent-cloud', 'Kkt'], ['openrouter', 'Wkt'], ['ollama', 'Hkt']]) {
const node = contexts.definitions.get(binding), value = node?.type === 'TemplateLiteral' && !node.expressions.length ? node.quasis[0].value.cooked : ''
if (!value.startsWith('data:image/png;base64,')) throw Error(`Provider PNG not inline: ${vendor}`)
const bytes = Buffer.from(value.slice(value.indexOf(',') + 1), 'base64')
await fs.writeFile(`${out}/provider-assets/${vendor}.png`, bytes)
manifest.assets.push({ vendor, binding, offset: node.start, bytes: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') })
}
await fs.writeFile(`${out}/SourceProviderGlyphs.tsx`, `// @ts-nocheck\n// Pure source provider SVGs from REA ${evidence.evidence_id}.\nimport {jsx,jsxs} from 'react/jsx-runtime'\nimport {forwardRef,useId} from 'react'\n${vendorDefinitions.join('\n')}\nexport const sourceProviderGlyphs = {${Object.keys(vendors).map(vendor => `${JSON.stringify(vendor)}:Provider${vendor.replace(/(^|-)([a-z])/g, (_all, _dash, char) => char.toUpperCase())}Icon`).join(',')}}\n`)
const cssFile = '.tmp/rea-css-recovered/src-Bh3YZ35H.css', cssText = await fs.readFile(cssFile, 'utf8')
if (createHash('sha256').update(cssText).digest('hex') !== '88dbdc1cd3f59d0e8b75824512a465a2be714bc7af481d790fe3a9484f85e01a') throw Error('Shared CSS changed')
const selected = postcss.root()
function select(node) {
if (node.type === 'rule') {
if (!/\.wb-(?:select|popover|input|checkbox|tooltip)|\.wb-icon/.test(node.selector)) return null
const copy = node.clone(); copy.selectors = copy.selectors.map(selector => '.source-select-host ' + selector); return copy
}
if (node.type !== 'atrule') return null
if (/keyframes/.test(node.name)) return /spin|popover|tooltip/.test(node.params) ? node.clone() : null
const copy = node.clone({ nodes: [] }); for (const child of node.nodes ?? []) { const match = select(child); if (match) copy.append(match) }
return copy.nodes.length ? copy : null
}
for (const node of postcss.parse(cssText).nodes) { const match = select(node); if (match) selected.append(match) }
const modelCss = panel.normalized_result.files.find(file => file.path.endsWith('ModelsSettingsPanel-FfiX-Du7.css')).text
postcss.parse(modelCss).walkRules(rule => { if (/models-settings-panel__provider-(?:option|icon-image)/.test(rule.selector)) { const copy = rule.clone(); copy.selectors = copy.selectors.map(selector => '.source-select-host ' + selector); selected.append(copy) } })
const needed = new Set(), collect = text => { for (const match of text.matchAll(/var\((--[\w-]+)/g)) needed.add(match[1]) }
collect(selected.toString())
collect(tooltip.join('\n'))
const tokenText = await fs.readFile('.tmp/rea-css-recovered/tokens-Df-i-zsO.css', 'utf8'), tokenRules = []
manifest.css = [{file:'src-Bh3YZ35H.css',sha256:createHash('sha256').update(cssText).digest('hex')},{file:'tokens-Df-i-zsO.css',sha256:createHash('sha256').update(tokenText).digest('hex')},{file:'ModelsSettingsPanel-FfiX-Du7.css',sha256:createHash('sha256').update(modelCss).digest('hex')}]
if (manifest.css[1].sha256 !== '13f2bf82c96e5167a7d7098e36950c6c22c475b3337dbb8bb29ad2fe79cde696') throw Error('Source tokens changed')
for (const text of [tokenText, cssText]) postcss.parse(text).walkRules(rule => tokenRules.push(rule))
for (let pass = 0; pass < 12; pass++) for (const rule of tokenRules) rule.walkDecls(d => { if (needed.has(d.prop)) collect(d.value) })
const light = new Map(), dark = new Map()
for (const rule of tokenRules) { const map = /dark/.test(rule.selector) ? dark : rule.selector.includes(':root') ? light : undefined; if (map) rule.walkDecls(d => { if (needed.has(d.prop)) map.set(d.prop, d.value) }) }
const serialize = map => [...map].map(([key, value]) => key + ':' + value).join(';')
await fs.writeFile(`${out}/source-select-recovered.css`, `/* Exact selected rule bodies/order/conditions from REA ev_377c8790468b3c67dad8577aabb327f98d3140659f0fffdbabfcff5a40cc9e94. */\n.source-select-host{${serialize(light)}}\n:root[data-theme=dark] .source-select-host,[data-cr-theme=dark] .source-select-host{${serialize(dark)}}\n${selected.toString()}\n`)
await fs.mkdir('.tmp/source-select-recovery', { recursive: true })
await fs.writeFile('.tmp/source-select-recovery/manifest.json', JSON.stringify(manifest, null, 2))
console.log(JSON.stringify({ definitions: manifest.definitions.length, icons: manifest.icons.length, assets: manifest.assets.length }))