fix(ai): require closed stock photo vectors

- Reject open vector networks before contacting the provider\n- Cover closed and open vector targets explicitly\n- Label the stock photo prompt example code fence
This commit is contained in:
Danila Poyarkov 2026-09-03 08:38:06 +03:00
parent 90b608863e
commit 9a3d7ca702
3 changed files with 71 additions and 2 deletions

View file

@ -51,6 +51,13 @@ export async function applyPhoto(
}
}
if (node.type === 'VECTOR' && (node.vectorNetwork.regions?.length ?? 0) === 0) {
return {
id: req.id,
error: `"${node.name}" has no closed vector regions — use closed area geometry`
}
}
if (node.type !== 'BOOLEAN_OPERATION' && node.children.length > 0) {
return { id: req.id, error: `"${node.name}" has children — use a leaf image placeholder` }
}

View file

@ -98,7 +98,7 @@ No style={{}}, className, CSS. No named colors or rgb(). No percentage values. N
`stock_photo` places real Pexels images on leaf image placeholders. Prefer Rectangle, Rounded Rectangle, or Ellipse placeholders; closed vector and Boolean geometry are also supported. Pass a JSON array — **all photos fetched in parallel**:
```
```text
stock_photo({ requests: '[{"id":"0:30","query":"wall street trading floor"},{"id":"0:58","query":"AI chip semiconductor"},{"id":"0:65","query":"bank finance credit card"}]' })
```

View file

@ -51,7 +51,6 @@ const AREA_TARGET_TYPES = [
'ELLIPSE',
'STAR',
'POLYGON',
'VECTOR',
'COMPONENT',
'INSTANCE'
] as const satisfies readonly NodeType[]
@ -105,6 +104,69 @@ describe('applyPhoto', () => {
})
}
test('applies a photo to closed vector geometry', async () => {
const { graph, page, figma } = setup()
const vector = graph.createNode('VECTOR', page.id, {
name: 'Closed vector',
width: 100,
height: 100,
vectorNetwork: {
vertices: [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
{ x: 100, y: 100 },
{ x: 0, y: 100 }
],
segments: [
{ start: 0, end: 1, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 1, end: 2, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 2, end: 3, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 3, end: 0, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } }
],
regions: [{ windingRule: 'NONZERO', loops: [[0, 1, 2, 3]] }]
}
})
const calls: ProviderCall[] = []
const result = await applyPhoto(figma, createProvider(calls), {
id: vector.id,
query: 'marble texture'
})
expect(result.error).toBeUndefined()
expect(calls).toHaveLength(1)
expect(vector.fills[0]).toMatchObject({ type: 'IMAGE', imageScaleMode: 'FILL' })
})
test('rejects open vector geometry before searching', async () => {
const { graph, page, figma } = setup()
const vector = graph.createNode('VECTOR', page.id, {
name: 'Open vector',
vectorNetwork: {
vertices: [
{ x: 0, y: 0 },
{ x: 100, y: 100 }
],
segments: [{ start: 0, end: 1, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } }],
regions: []
}
})
const originalFills = copyFills(vector.fills)
const calls: ProviderCall[] = []
const result = await applyPhoto(figma, createProvider(calls), {
id: vector.id,
query: 'should not run'
})
expect(result.error).toBe(
`"${vector.name}" has no closed vector regions — use closed area geometry`
)
expect(calls).toHaveLength(0)
expect(vector.fills).toEqual(originalFills)
expect(graph.images.size).toBe(0)
})
test('applies a photo to Boolean geometry with operand children', async () => {
const { graph, page, figma } = setup()
const operation = graph.createNode('BOOLEAN_OPERATION', page.id, {