Skip to content

Commit 513ea06

Browse files
committed
feat: rewrite the main pipeline
1 parent a1d4059 commit 513ea06

37 files changed

+2097
-572
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { AssetDescriptor } from '@tempad-dev/mcp-shared'
2+
3+
import type { CodegenConfig } from '@/utils/codegen'
4+
5+
import type { VisibleTree } from '../model'
6+
import type { AssetPlan } from './plan'
7+
import type { SvgEntry } from './vector'
8+
9+
import { exportSvgEntry } from './vector'
10+
11+
export async function exportVectorAssets(
12+
tree: VisibleTree,
13+
plan: AssetPlan,
14+
config: CodegenConfig,
15+
assetRegistry: Map<string, AssetDescriptor>
16+
): Promise<Map<string, SvgEntry>> {
17+
const svgs = new Map<string, SvgEntry>()
18+
for (const id of plan.vectorRoots) {
19+
const snapshot = tree.nodes.get(id)
20+
if (!snapshot) continue
21+
const node = snapshot.node
22+
const { width, height } = snapshot.bounds
23+
if (width <= 0 && height <= 0 && !snapshot.renderBounds) continue
24+
const entry = await exportSvgEntry(node, config, assetRegistry)
25+
if (!entry) continue
26+
svgs.set(id, entry)
27+
}
28+
return svgs
29+
}

packages/extension/mcp/tools/code/assets/image.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { CodegenConfig } from '@/utils/codegen'
44

55
import { ensureAssetUploaded } from '@/mcp/assets'
66
import { BG_URL_RE } from '@/utils/css'
7+
import { toDecimalPlace } from '@/utils/number'
78

89
const imageBytesCache = new Map<string, Promise<Uint8Array>>()
910

@@ -55,8 +56,12 @@ function replaceImageUrlsWithPlaceholder(
5556
let w = 100
5657
let h = 100
5758

58-
if ('width' in node && typeof node.width === 'number') w = Math.round(node.width * scale)
59-
if ('height' in node && typeof node.height === 'number') h = Math.round(node.height * scale)
59+
if ('width' in node && typeof node.width === 'number') {
60+
w = Math.round(toDecimalPlace(node.width) * scale)
61+
}
62+
if ('height' in node && typeof node.height === 'number') {
63+
h = Math.round(toDecimalPlace(node.height) * scale)
64+
}
6065

6166
const placeholderUrl = `https://placehold.co/${w}x${h}`
6267
const result = { ...style }
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export * from './vector'
22
export * from './image'
3+
export * from './plan'
4+
export * from './export'
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import type { NodeSnapshot, VisibleTree } from '../model'
2+
3+
export type AssetPlan = {
4+
vectorRoots: Set<string>
5+
}
6+
7+
export function planAssets(tree: VisibleTree): AssetPlan {
8+
const vectorRoots = new Set<string>()
9+
const skipped = new Set<string>()
10+
const vectorInfo = computeVectorInfo(tree)
11+
12+
for (const id of tree.order) {
13+
if (skipped.has(id)) continue
14+
const node = tree.nodes.get(id)
15+
if (!node) continue
16+
17+
const children = node.children
18+
.map((childId) => tree.nodes.get(childId))
19+
.filter(Boolean) as NodeSnapshot[]
20+
21+
const info = vectorInfo.get(id)
22+
const isVectorGroup = !!info && info.allVector && info.leafCount > 1 && node.children.length > 1
23+
24+
if (isVectorGroup) {
25+
vectorRoots.add(id)
26+
children.forEach((child) => skipDescendants(child.id, tree, skipped))
27+
continue
28+
}
29+
30+
if (node.assetKind === 'vector') {
31+
vectorRoots.add(id)
32+
}
33+
}
34+
35+
return { vectorRoots }
36+
}
37+
38+
function computeVectorInfo(
39+
tree: VisibleTree
40+
): Map<string, { allVector: boolean; leafCount: number }> {
41+
const info = new Map<string, { allVector: boolean; leafCount: number }>()
42+
43+
for (let i = tree.order.length - 1; i >= 0; i--) {
44+
const id = tree.order[i]
45+
const node = tree.nodes.get(id)
46+
if (!node) continue
47+
48+
if (!node.children.length) {
49+
const isVector = node.assetKind === 'vector'
50+
info.set(id, { allVector: isVector, leafCount: isVector ? 1 : 0 })
51+
continue
52+
}
53+
54+
let allVector = true
55+
let leafCount = 0
56+
for (const childId of node.children) {
57+
const childInfo = info.get(childId)
58+
if (!childInfo || !childInfo.allVector) {
59+
allVector = false
60+
}
61+
if (childInfo) leafCount += childInfo.leafCount
62+
}
63+
info.set(id, { allVector, leafCount })
64+
}
65+
66+
return info
67+
}
68+
69+
function skipDescendants(id: string, tree: VisibleTree, skipped: Set<string>): void {
70+
const node = tree.nodes.get(id)
71+
if (!node) return
72+
if (skipped.has(id)) return
73+
skipped.add(id)
74+
node.children.forEach((childId) => skipDescendants(childId, tree, skipped))
75+
}

packages/extension/mcp/tools/code/assets/vector.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { CodegenConfig } from '@/utils/codegen'
44

55
import { ensureAssetUploaded } from '@/mcp/assets'
66
import { normalizeCssValue } from '@/utils/css'
7+
import { toDecimalPlace } from '@/utils/number'
78

89
export type SvgEntry = {
910
props: Record<string, string>
@@ -29,8 +30,8 @@ export async function exportSvgEntry(
2930
}
3031
try {
3132
const asset = await ensureAssetUploaded(svgUint8, 'image/svg+xml', {
32-
width: Math.round(node.width),
33-
height: Math.round(node.height)
33+
width: Math.round(toDecimalPlace(node.width)),
34+
height: Math.round(toDecimalPlace(node.height))
3435
})
3536
assetRegistry.set(asset.hash, asset)
3637
baseProps['data-resource-uri'] = asset.resourceUri
@@ -41,7 +42,13 @@ export async function exportSvgEntry(
4142
}
4243
} catch (error) {
4344
console.warn('[tempad-dev] Failed to export vector node:', error)
44-
return null
45+
return {
46+
props: {
47+
width: `${toDecimalPlace(node.width)}px`,
48+
height: `${toDecimalPlace(node.height)}px`
49+
},
50+
raw: '<svg></svg>'
51+
}
4552
}
4653
}
4754

0 commit comments

Comments
 (0)