ima2-gen 3.12.2 → 3.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/bin/commands/vectorize.js +108 -0
  2. package/bin/ima2.js +5 -1
  3. package/docs/API.md +1 -1
  4. package/docs/API.zh-CN.md +1 -1
  5. package/docs/API.zh-TW.md +1 -1
  6. package/docs/CLI.md +1 -0
  7. package/docs/migration/runtime-test-inventory.md +6 -1
  8. package/lib/agentImageVideoGen.js +15 -1
  9. package/lib/agentQueueStore.js +11 -3
  10. package/lib/agentQueueWorker.js +6 -1
  11. package/lib/agentRuntime.js +4 -0
  12. package/lib/db.js +3 -0
  13. package/lib/imageThumb.js +9 -2
  14. package/lib/vectorizeImage.js +132 -0
  15. package/package.json +4 -3
  16. package/routes/assetDerived.js +79 -2
  17. package/server.js +8 -0
  18. package/skills/ima2/SKILL.md +37 -0
  19. package/ui/dist/.vite/manifest.json +33 -33
  20. package/ui/dist/assets/{AgentWorkspace-CW1x4uOl.js → AgentWorkspace-AJkVTaU5.js} +2 -2
  21. package/ui/dist/assets/AssetGenWorkspace-Cguej69V.js +2 -0
  22. package/ui/dist/assets/AssetsWorkspace-DIQ9wBuD.js +1 -0
  23. package/ui/dist/assets/{CardNewsWorkspace-nBKX5TXr.js → CardNewsWorkspace-C_Eb2g7d.js} +1 -1
  24. package/ui/dist/assets/{GenerationRequestLogPanel-CWqbfNis.js → GenerationRequestLogPanel-giH1e4hp.js} +1 -1
  25. package/ui/dist/assets/{HomeWorkspace-CxqIGhb9.js → HomeWorkspace-DHmXGtqC.js} +1 -1
  26. package/ui/dist/assets/{NodeCanvas-m41ECrFJ.js → NodeCanvas-CF_NHudI.js} +1 -1
  27. package/ui/dist/assets/{PromptBuilderPanel-ChOoMAVA.js → PromptBuilderPanel-D7NJto-1.js} +2 -2
  28. package/ui/dist/assets/{PromptImportDialog-8hDG8MeK.js → PromptImportDialog-B40X9UAa.js} +2 -2
  29. package/ui/dist/assets/{PromptImportDiscoverySection-D2_TAuLm.js → PromptImportDiscoverySection-BWoUDHj1.js} +1 -1
  30. package/ui/dist/assets/{PromptImportFolderSection-qv0qhTwq.js → PromptImportFolderSection-DBSEyJb-.js} +1 -1
  31. package/ui/dist/assets/{PromptLibraryPanel-BPOD61FZ.js → PromptLibraryPanel-DLywJBpt.js} +2 -2
  32. package/ui/dist/assets/{SettingsWorkspace-BKgXN2l9.js → SettingsWorkspace-BawWId1t.js} +1 -1
  33. package/ui/dist/assets/SpriteRecipeWorkspace-Biy2yRAk.js +1 -0
  34. package/ui/dist/assets/VectorizePanel-DdlVvwV1.js +1 -0
  35. package/ui/dist/assets/{index-Bzt4YWub.js → index-D05Ong5g.js} +3 -3
  36. package/ui/dist/assets/{index-DB9EfKr6.css → index-DGRIdnsL.css} +1 -1
  37. package/ui/dist/assets/{index-DTEhasVd.js → index-Sch70vBs.js} +17 -17
  38. package/ui/dist/assets/{pptxgen.es-BEVsR2-i.js → pptxgen.es-CSd_gCsx.js} +1 -1
  39. package/ui/dist/assets/useAgentDialogFocus-DcWwFfYd.js +1 -0
  40. package/ui/dist/index.html +2 -2
  41. package/ui/dist/assets/AssetGenWorkspace-BQkdilsH.js +0 -2
  42. package/ui/dist/assets/AssetsWorkspace-CYSkmYU-.js +0 -1
  43. package/ui/dist/assets/KeyingPanel-CtGvO6-u.js +0 -1
  44. package/ui/dist/assets/SpriteRecipeWorkspace-D8gHgnt8.js +0 -1
  45. package/ui/dist/assets/useAgentDialogFocus-Dyj_bA5Q.js +0 -1
@@ -1,17 +1,18 @@
1
1
  import express from "express";
2
2
  import { existsSync } from "fs";
3
- import { writeFile } from "fs/promises";
3
+ import { readFile, writeFile } from "fs/promises";
4
4
  import { basename } from "path";
5
5
  import { config } from "../config.js";
6
6
  import { errInfo } from "../lib/errInfo.js";
7
7
  import { logEvent } from "../lib/logger.js";
8
8
  import { requireRuntimeContext } from "../lib/runtimeContext.js";
9
9
  import { assertRegularGeneratedPath, resolveInGenerated } from "../lib/assetLifecycle.js";
10
+ import { isRasterPath, isVectorPreset, vectorizeImageBuffer } from "../lib/vectorizeImage.js";
10
11
  import { createAsset } from "../lib/assetsStore.js";
11
12
  import { safeWriteSidecar } from "../lib/atomicWrite.js";
12
13
  import { invalidateHistoryIndex } from "../lib/historyIndex.js";
13
14
  const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
14
- const DERIVED_KINDS = ["keyed-png"];
15
+ const DERIVED_KINDS = ["keyed-png", "vector-svg"];
15
16
  const MAX_META_BYTES = 2048;
16
17
  function httpError(status, code, message) {
17
18
  const err = new Error(message);
@@ -34,6 +35,78 @@ function parseMeta(raw) {
34
35
  throw httpError(400, "DERIVED_META_INVALID", "meta query must be a JSON object");
35
36
  }
36
37
  }
38
+ function numericQuery(raw) {
39
+ if (typeof raw !== "string" || raw.trim() === "")
40
+ return undefined;
41
+ const parsed = Number(raw);
42
+ return Number.isFinite(parsed) ? parsed : undefined;
43
+ }
44
+ /**
45
+ * Trace an existing generated raster into SVG.
46
+ *
47
+ * The client sends NO body: the source is already validated inside generated
48
+ * storage, so the server reads it directly. That avoids re-uploading megabytes the
49
+ * server already has, and keeps arbitrary XML off the request surface.
50
+ */
51
+ async function handleVectorSvg(req, res, source) {
52
+ const { sourceRel, sourceAbs } = source;
53
+ if (!isRasterPath(sourceRel)) {
54
+ // Guards against tracing an existing .svg (no recursion) and against video
55
+ // files, which already live in the same directory.
56
+ throw httpError(400, "DERIVED_SOURCE_NOT_RASTER", "source must be a png, jpeg, or webp image");
57
+ }
58
+ const presetRaw = typeof req.query.preset === "string" ? req.query.preset : "auto";
59
+ if (!isVectorPreset(presetRaw)) {
60
+ throw httpError(400, "DERIVED_PRESET_INVALID", "preset must be one of: auto, flat, detailed, mono");
61
+ }
62
+ const preset = presetRaw;
63
+ const traced = await vectorizeImageBuffer(await readFile(sourceAbs), {
64
+ preset,
65
+ colorPrecision: numericQuery(req.query.colorPrecision),
66
+ filterSpeckle: numericQuery(req.query.filterSpeckle),
67
+ cornerThreshold: numericQuery(req.query.cornerThreshold),
68
+ });
69
+ const projectId = typeof req.query.projectId === "string" && req.query.projectId ? req.query.projectId : undefined;
70
+ const meta = parseMeta(req.query.meta);
71
+ const stem = basename(sourceRel).replace(/\.[a-z0-9]+$/i, "");
72
+ const outName = `${stem}-vector-${Date.now()}.svg`;
73
+ const outAbs = resolveInGenerated(config.storage.generatedDir, outName);
74
+ await writeFile(outAbs, traced.svg, "utf8");
75
+ await safeWriteSidecar(`${outAbs}.json`, {
76
+ kind: "vector-svg",
77
+ derivedFrom: sourceRel,
78
+ createdAt: Date.now(),
79
+ preset: traced.preset,
80
+ pathCount: traced.pathCount,
81
+ ...meta,
82
+ });
83
+ const asset = createAsset({
84
+ kind: "image",
85
+ name: typeof req.query.name === "string" && req.query.name ? req.query.name.slice(0, 80) : outName,
86
+ filePath: outName,
87
+ folderId: projectId,
88
+ notes: undefined,
89
+ metadata: {
90
+ derivedFrom: sourceRel,
91
+ derivedKind: "vector-svg",
92
+ vector: true,
93
+ preset: traced.preset,
94
+ pathCount: traced.pathCount,
95
+ ...meta,
96
+ },
97
+ tags: [],
98
+ });
99
+ invalidateHistoryIndex();
100
+ logEvent("assets", "derived-create", { assetId: asset.id, kind: "vector-svg", source: sourceRel });
101
+ res.status(201).json({
102
+ filePath: outName,
103
+ asset,
104
+ pathCount: traced.pathCount,
105
+ bytes: traced.bytes,
106
+ elapsedMs: traced.elapsedMs,
107
+ preset: traced.preset,
108
+ });
109
+ }
37
110
  export function registerAssetDerivedRoutes(app, ctxRaw) {
38
111
  requireRuntimeContext(ctxRaw);
39
112
  const rawPng = express.raw({ type: "image/png", limit: "30mb" });
@@ -50,6 +123,10 @@ export function registerAssetDerivedRoutes(app, ctxRaw) {
50
123
  if (!existsSync(sourceAbs))
51
124
  throw httpError(400, "DERIVED_SOURCE_MISSING", "source does not exist in generated storage");
52
125
  await assertRegularGeneratedPath(sourceAbs);
126
+ if (kind === "vector-svg") {
127
+ await handleVectorSvg(req, res, { sourceRel, sourceAbs });
128
+ return;
129
+ }
53
130
  const body = req.body;
54
131
  if (!Buffer.isBuffer(body) || body.length < PNG_SIGNATURE.length || !body.subarray(0, 8).equals(PNG_SIGNATURE)) {
55
132
  throw httpError(400, "DERIVED_BODY_NOT_PNG", "body must be a PNG (image/png raw body)");
package/server.js CHANGED
@@ -270,6 +270,14 @@ export function buildApp(ctx) {
270
270
  app.use("/generated", (req, res, next) => {
271
271
  if (req.path.endsWith(".json"))
272
272
  return res.status(404).type("text/plain").send("Generated metadata is not public");
273
+ if (req.path.toLowerCase().endsWith(".svg")) {
274
+ // SVG is an active document under top-level navigation, and /generated sits
275
+ // outside the LAN token guard (which only covers /api). Traced output is
276
+ // machine-generated, but the guarantee should not rest on writer discipline:
277
+ // this neuters script execution for any .svg served from here, now or later.
278
+ res.setHeader("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'");
279
+ res.setHeader("X-Content-Type-Options", "nosniff");
280
+ }
273
281
  return next();
274
282
  }, express.static(ctx.config.storage.generatedDir, {
275
283
  maxAge: ctx.config.storage.staticMaxAge,
@@ -387,6 +387,43 @@ pattern painted into an opaque image; the flag sends the real API parameter and
387
387
  the tuned suffix together. Always verify alpha rather than trusting the look of
388
388
  a preview: `sharp(file).metadata()` should report `channels: 4, hasAlpha: true`.
389
389
 
390
+ ### Raster-to-Vector (SVG)
391
+
392
+ `ima2 gen` always produces raster. When you need a REAL vector — a logo that
393
+ scales, an icon for a design system, a brand-safe SVG asset — trace the raster
394
+ afterwards. `flat vector style` in a prompt only produces a bitmap that LOOKS
395
+ vector; it has no paths.
396
+
397
+ ```bash
398
+ # 1. generate a cutout, 2. trace it
399
+ ima2 gen "a minimal geometric fox head logo mark, flat vector style" \
400
+ --bg transparent --quality high --mode direct -o logo.png
401
+ ima2 vectorize logo.png -o logo.svg --json
402
+ ```
403
+
404
+ Runs fully local — no server, no provider, no network. `--json` reports
405
+ `pathCount`, `bytes`, and `elapsedMs` so an agent can check the result
406
+ programmatically.
407
+
408
+ Presets: `auto` (default, best colour fidelity), `flat` (fewer colour layers),
409
+ `detailed`, and `mono` (binary silhouette). Tune with `--color-precision 1-8`,
410
+ `--filter-speckle 0-128`, `--corner-threshold 0-180`; pass a knob only when you
411
+ actually want to override the preset, since any value replaces the tuned config.
412
+
413
+ **Where it works and where it does not.** Measured on a 1254x1254 keyed
414
+ character asset: `auto` produced 722 paths at 585KB and is visually
415
+ indistinguishable from the source. Cutouts, icons, logos, flat art, and sprite
416
+ frames trace cleanly. Photographs and small text do NOT — text becomes
417
+ illegible smudges and gradients fragment into hundreds of bands. Key or
418
+ generate a clean cutout FIRST; tracing quality follows input flatness.
419
+
420
+ `mono` has no alpha channel, so a transparent cutout becomes a silhouette on a
421
+ black field. Use it for stencils and line art, not for cutouts.
422
+
423
+ In the app, the same operation is the "Convert to SVG" action on any image
424
+ asset (generation grid tile or Assets library preview), which saves the SVG
425
+ into the current project.
426
+
390
427
  ### Korean Text in Images
391
428
 
392
429
  When generating images with Korean text:
@@ -3,13 +3,13 @@
3
3
  "file": "assets/AssetGenWorkspace-B7tTK0Sx.css",
4
4
  "src": "_AssetGenWorkspace-B7tTK0Sx.css"
5
5
  },
6
- "_AssetGenWorkspace-BQkdilsH.js": {
7
- "file": "assets/AssetGenWorkspace-BQkdilsH.js",
6
+ "_AssetGenWorkspace-Cguej69V.js": {
7
+ "file": "assets/AssetGenWorkspace-Cguej69V.js",
8
8
  "name": "AssetGenWorkspace",
9
9
  "isDynamicEntry": true,
10
10
  "imports": [
11
11
  "index.html",
12
- "_KeyingPanel-CtGvO6-u.js"
12
+ "_VectorizePanel-DdlVvwV1.js"
13
13
  ],
14
14
  "dynamicImports": [
15
15
  "src/components/assetgen/SpriteRecipeWorkspace.tsx"
@@ -18,12 +18,12 @@
18
18
  "assets/AssetGenWorkspace-B7tTK0Sx.css"
19
19
  ]
20
20
  },
21
- "_KeyingPanel-CtGvO6-u.js": {
22
- "file": "assets/KeyingPanel-CtGvO6-u.js",
23
- "name": "KeyingPanel",
21
+ "_VectorizePanel-DdlVvwV1.js": {
22
+ "file": "assets/VectorizePanel-DdlVvwV1.js",
23
+ "name": "VectorizePanel",
24
24
  "imports": [
25
25
  "index.html",
26
- "_useAgentDialogFocus-Dyj_bA5Q.js"
26
+ "_useAgentDialogFocus-DcWwFfYd.js"
27
27
  ]
28
28
  },
29
29
  "__vite-browser-external": {
@@ -32,15 +32,15 @@
32
32
  "src": "__vite-browser-external",
33
33
  "isDynamicEntry": true
34
34
  },
35
- "_useAgentDialogFocus-Dyj_bA5Q.js": {
36
- "file": "assets/useAgentDialogFocus-Dyj_bA5Q.js",
35
+ "_useAgentDialogFocus-DcWwFfYd.js": {
36
+ "file": "assets/useAgentDialogFocus-DcWwFfYd.js",
37
37
  "name": "useAgentDialogFocus",
38
38
  "imports": [
39
39
  "index.html"
40
40
  ]
41
41
  },
42
42
  "index.html": {
43
- "file": "assets/index-DTEhasVd.js",
43
+ "file": "assets/index-Sch70vBs.js",
44
44
  "name": "index",
45
45
  "src": "index.html",
46
46
  "isEntry": true,
@@ -55,16 +55,16 @@
55
55
  "src/components/card-news/CardNewsWorkspace.tsx",
56
56
  "src/components/agent/AgentWorkspace.tsx",
57
57
  "src/components/assets/AssetsWorkspace.tsx",
58
- "_AssetGenWorkspace-BQkdilsH.js",
58
+ "_AssetGenWorkspace-Cguej69V.js",
59
59
  "src/components/home/HomeWorkspace.tsx",
60
60
  "src/components/PromptLibraryPanel.tsx"
61
61
  ],
62
62
  "css": [
63
- "assets/index-DB9EfKr6.css"
63
+ "assets/index-DGRIdnsL.css"
64
64
  ]
65
65
  },
66
66
  "node_modules/pptxgenjs/dist/pptxgen.es.js": {
67
- "file": "assets/pptxgen.es-BEVsR2-i.js",
67
+ "file": "assets/pptxgen.es-CSd_gCsx.js",
68
68
  "name": "pptxgen.es",
69
69
  "src": "node_modules/pptxgenjs/dist/pptxgen.es.js",
70
70
  "isDynamicEntry": true,
@@ -78,7 +78,7 @@
78
78
  ]
79
79
  },
80
80
  "src/components/GenerationRequestLogPanel.tsx": {
81
- "file": "assets/GenerationRequestLogPanel-CWqbfNis.js",
81
+ "file": "assets/GenerationRequestLogPanel-giH1e4hp.js",
82
82
  "name": "GenerationRequestLogPanel",
83
83
  "src": "src/components/GenerationRequestLogPanel.tsx",
84
84
  "isDynamicEntry": true,
@@ -87,7 +87,7 @@
87
87
  ]
88
88
  },
89
89
  "src/components/NodeCanvas.tsx": {
90
- "file": "assets/NodeCanvas-m41ECrFJ.js",
90
+ "file": "assets/NodeCanvas-CF_NHudI.js",
91
91
  "name": "NodeCanvas",
92
92
  "src": "src/components/NodeCanvas.tsx",
93
93
  "isDynamicEntry": true,
@@ -99,7 +99,7 @@
99
99
  ]
100
100
  },
101
101
  "src/components/PromptImportDialog.tsx": {
102
- "file": "assets/PromptImportDialog-8hDG8MeK.js",
102
+ "file": "assets/PromptImportDialog-B40X9UAa.js",
103
103
  "name": "PromptImportDialog",
104
104
  "src": "src/components/PromptImportDialog.tsx",
105
105
  "isDynamicEntry": true,
@@ -112,7 +112,7 @@
112
112
  ]
113
113
  },
114
114
  "src/components/PromptImportDiscoverySection.tsx": {
115
- "file": "assets/PromptImportDiscoverySection-D2_TAuLm.js",
115
+ "file": "assets/PromptImportDiscoverySection-BWoUDHj1.js",
116
116
  "name": "PromptImportDiscoverySection",
117
117
  "src": "src/components/PromptImportDiscoverySection.tsx",
118
118
  "isDynamicEntry": true,
@@ -121,7 +121,7 @@
121
121
  ]
122
122
  },
123
123
  "src/components/PromptImportFolderSection.tsx": {
124
- "file": "assets/PromptImportFolderSection-qv0qhTwq.js",
124
+ "file": "assets/PromptImportFolderSection-DBSEyJb-.js",
125
125
  "name": "PromptImportFolderSection",
126
126
  "src": "src/components/PromptImportFolderSection.tsx",
127
127
  "isDynamicEntry": true,
@@ -130,7 +130,7 @@
130
130
  ]
131
131
  },
132
132
  "src/components/PromptLibraryPanel.tsx": {
133
- "file": "assets/PromptLibraryPanel-BPOD61FZ.js",
133
+ "file": "assets/PromptLibraryPanel-DLywJBpt.js",
134
134
  "name": "PromptLibraryPanel",
135
135
  "src": "src/components/PromptLibraryPanel.tsx",
136
136
  "isDynamicEntry": true,
@@ -142,7 +142,7 @@
142
142
  ]
143
143
  },
144
144
  "src/components/SettingsWorkspace.tsx": {
145
- "file": "assets/SettingsWorkspace-BKgXN2l9.js",
145
+ "file": "assets/SettingsWorkspace-BawWId1t.js",
146
146
  "name": "SettingsWorkspace",
147
147
  "src": "src/components/SettingsWorkspace.tsx",
148
148
  "isDynamicEntry": true,
@@ -151,43 +151,43 @@
151
151
  ]
152
152
  },
153
153
  "src/components/agent/AgentWorkspace.tsx": {
154
- "file": "assets/AgentWorkspace-CW1x4uOl.js",
154
+ "file": "assets/AgentWorkspace-AJkVTaU5.js",
155
155
  "name": "AgentWorkspace",
156
156
  "src": "src/components/agent/AgentWorkspace.tsx",
157
157
  "isDynamicEntry": true,
158
158
  "imports": [
159
159
  "index.html",
160
- "_useAgentDialogFocus-Dyj_bA5Q.js"
160
+ "_useAgentDialogFocus-DcWwFfYd.js"
161
161
  ]
162
162
  },
163
163
  "src/components/assetgen/SpriteRecipeWorkspace.tsx": {
164
- "file": "assets/SpriteRecipeWorkspace-D8gHgnt8.js",
164
+ "file": "assets/SpriteRecipeWorkspace-Biy2yRAk.js",
165
165
  "name": "SpriteRecipeWorkspace",
166
166
  "src": "src/components/assetgen/SpriteRecipeWorkspace.tsx",
167
167
  "isDynamicEntry": true,
168
168
  "imports": [
169
169
  "index.html",
170
- "_AssetGenWorkspace-BQkdilsH.js",
171
- "_KeyingPanel-CtGvO6-u.js",
172
- "_useAgentDialogFocus-Dyj_bA5Q.js"
170
+ "_AssetGenWorkspace-Cguej69V.js",
171
+ "_VectorizePanel-DdlVvwV1.js",
172
+ "_useAgentDialogFocus-DcWwFfYd.js"
173
173
  ]
174
174
  },
175
175
  "src/components/assets/AssetsWorkspace.tsx": {
176
- "file": "assets/AssetsWorkspace-CYSkmYU-.js",
176
+ "file": "assets/AssetsWorkspace-DIQ9wBuD.js",
177
177
  "name": "AssetsWorkspace",
178
178
  "src": "src/components/assets/AssetsWorkspace.tsx",
179
179
  "isDynamicEntry": true,
180
180
  "imports": [
181
181
  "index.html",
182
- "_KeyingPanel-CtGvO6-u.js",
183
- "_useAgentDialogFocus-Dyj_bA5Q.js"
182
+ "_VectorizePanel-DdlVvwV1.js",
183
+ "_useAgentDialogFocus-DcWwFfYd.js"
184
184
  ],
185
185
  "css": [
186
186
  "assets/AssetsWorkspace-BdptASFf.css"
187
187
  ]
188
188
  },
189
189
  "src/components/canvas-mode/index.ts": {
190
- "file": "assets/index-Bzt4YWub.js",
190
+ "file": "assets/index-D05Ong5g.js",
191
191
  "name": "index",
192
192
  "src": "src/components/canvas-mode/index.ts",
193
193
  "isDynamicEntry": true,
@@ -199,7 +199,7 @@
199
199
  ]
200
200
  },
201
201
  "src/components/card-news/CardNewsWorkspace.tsx": {
202
- "file": "assets/CardNewsWorkspace-nBKX5TXr.js",
202
+ "file": "assets/CardNewsWorkspace-C_Eb2g7d.js",
203
203
  "name": "CardNewsWorkspace",
204
204
  "src": "src/components/card-news/CardNewsWorkspace.tsx",
205
205
  "isDynamicEntry": true,
@@ -208,7 +208,7 @@
208
208
  ]
209
209
  },
210
210
  "src/components/home/HomeWorkspace.tsx": {
211
- "file": "assets/HomeWorkspace-CxqIGhb9.js",
211
+ "file": "assets/HomeWorkspace-DHmXGtqC.js",
212
212
  "name": "HomeWorkspace",
213
213
  "src": "src/components/home/HomeWorkspace.tsx",
214
214
  "isDynamicEntry": true,
@@ -217,7 +217,7 @@
217
217
  ]
218
218
  },
219
219
  "src/components/prompt-builder/PromptBuilderPanel.tsx": {
220
- "file": "assets/PromptBuilderPanel-ChOoMAVA.js",
220
+ "file": "assets/PromptBuilderPanel-D7NJto-1.js",
221
221
  "name": "PromptBuilderPanel",
222
222
  "src": "src/components/prompt-builder/PromptBuilderPanel.tsx",
223
223
  "isDynamicEntry": true,