@lovision/plugin-dev 1.0.0

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 (96) hide show
  1. package/README.md +49 -0
  2. package/dist/build.d.ts +16 -0
  3. package/dist/build.d.ts.map +1 -0
  4. package/dist/build.js +108 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +123 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/create-plugin.d.ts +19 -0
  11. package/dist/create-plugin.d.ts.map +1 -0
  12. package/dist/create-plugin.js +186 -0
  13. package/dist/create-plugin.js.map +1 -0
  14. package/dist/dev.d.ts +13 -0
  15. package/dist/dev.d.ts.map +1 -0
  16. package/dist/dev.js +206 -0
  17. package/dist/dev.js.map +1 -0
  18. package/dist/index.d.ts +13 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +7 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/publish.d.ts +15 -0
  23. package/dist/publish.d.ts.map +1 -0
  24. package/dist/publish.js +55 -0
  25. package/dist/publish.js.map +1 -0
  26. package/dist/shared.d.ts +93 -0
  27. package/dist/shared.d.ts.map +1 -0
  28. package/dist/shared.js +436 -0
  29. package/dist/shared.js.map +1 -0
  30. package/dist/templates/ai-layout-assistant/README.md.template +24 -0
  31. package/dist/templates/ai-layout-assistant/eslint.config.mjs +19 -0
  32. package/dist/templates/ai-layout-assistant/manifest.json.template +38 -0
  33. package/dist/templates/ai-layout-assistant/package.json.template +18 -0
  34. package/dist/templates/ai-layout-assistant/src/main.ts.template +345 -0
  35. package/dist/templates/ai-layout-assistant/tsconfig.json +14 -0
  36. package/dist/templates/ai-layout-assistant/ui.html.template +114 -0
  37. package/dist/templates/asset-browser/README.md.template +24 -0
  38. package/dist/templates/asset-browser/eslint.config.mjs +19 -0
  39. package/dist/templates/asset-browser/manifest.json.template +29 -0
  40. package/dist/templates/asset-browser/package.json.template +18 -0
  41. package/dist/templates/asset-browser/src/main.ts.template +177 -0
  42. package/dist/templates/asset-browser/tsconfig.json +14 -0
  43. package/dist/templates/asset-browser/ui.html.template +137 -0
  44. package/dist/templates/base/README.md.template +34 -0
  45. package/dist/templates/base/eslint.config.mjs +19 -0
  46. package/dist/templates/base/manifest.json.template +22 -0
  47. package/dist/templates/base/package.json.template +18 -0
  48. package/dist/templates/base/src/main.ts.template +20 -0
  49. package/dist/templates/base/tsconfig.json +14 -0
  50. package/dist/templates/batch-layout-organizer/README.md.template +24 -0
  51. package/dist/templates/batch-layout-organizer/eslint.config.mjs +19 -0
  52. package/dist/templates/batch-layout-organizer/manifest.json.template +31 -0
  53. package/dist/templates/batch-layout-organizer/package.json.template +18 -0
  54. package/dist/templates/batch-layout-organizer/src/main.ts.template +324 -0
  55. package/dist/templates/batch-layout-organizer/tsconfig.json +14 -0
  56. package/dist/templates/batch-layout-organizer/ui.html.template +116 -0
  57. package/dist/templates/data-filler-full/README.md.template +32 -0
  58. package/dist/templates/data-filler-full/eslint.config.mjs +19 -0
  59. package/dist/templates/data-filler-full/manifest.json.template +31 -0
  60. package/dist/templates/data-filler-full/package.json.template +18 -0
  61. package/dist/templates/data-filler-full/src/main.ts.template +412 -0
  62. package/dist/templates/data-filler-full/tsconfig.json +14 -0
  63. package/dist/templates/data-filler-full/ui.html.template +221 -0
  64. package/dist/templates/data-filler-lite/README.md.template +47 -0
  65. package/dist/templates/data-filler-lite/eslint.config.mjs +19 -0
  66. package/dist/templates/data-filler-lite/manifest.json.template +29 -0
  67. package/dist/templates/data-filler-lite/package.json.template +18 -0
  68. package/dist/templates/data-filler-lite/src/main.ts.template +222 -0
  69. package/dist/templates/data-filler-lite/tsconfig.json +14 -0
  70. package/dist/templates/data-filler-lite/ui.html.template +180 -0
  71. package/dist/templates/design-lint-panel/README.md.template +33 -0
  72. package/dist/templates/design-lint-panel/eslint.config.mjs +19 -0
  73. package/dist/templates/design-lint-panel/manifest.json.template +29 -0
  74. package/dist/templates/design-lint-panel/package.json.template +18 -0
  75. package/dist/templates/design-lint-panel/src/main.ts.template +221 -0
  76. package/dist/templates/design-lint-panel/tsconfig.json +14 -0
  77. package/dist/templates/design-lint-panel/ui.html.template +172 -0
  78. package/dist/templates/export-selection/README.md.template +26 -0
  79. package/dist/templates/export-selection/eslint.config.mjs +19 -0
  80. package/dist/templates/export-selection/manifest.json.template +31 -0
  81. package/dist/templates/export-selection/package.json.template +18 -0
  82. package/dist/templates/export-selection/src/main.ts.template +386 -0
  83. package/dist/templates/export-selection/tsconfig.json +14 -0
  84. package/dist/templates/export-selection/ui.html.template +163 -0
  85. package/dist/templates/review-submitter/README.md.template +24 -0
  86. package/dist/templates/review-submitter/eslint.config.mjs +19 -0
  87. package/dist/templates/review-submitter/manifest.json.template +35 -0
  88. package/dist/templates/review-submitter/package.json.template +18 -0
  89. package/dist/templates/review-submitter/src/main.ts.template +306 -0
  90. package/dist/templates/review-submitter/tsconfig.json +14 -0
  91. package/dist/templates/review-submitter/ui.html.template +114 -0
  92. package/dist/validate.d.ts +8 -0
  93. package/dist/validate.d.ts.map +1 -0
  94. package/dist/validate.js +42 -0
  95. package/dist/validate.js.map +1 -0
  96. package/package.json +46 -0
@@ -0,0 +1,345 @@
1
+ import { definePlugin } from "@lovision/plugin-sdk";
2
+
3
+ type Vec2 = {
4
+ x: number;
5
+ y: number;
6
+ };
7
+
8
+ type Size = {
9
+ height: number;
10
+ width: number;
11
+ };
12
+
13
+ type SceneNodeSnapshot = {
14
+ children: SceneNodeSnapshot[];
15
+ id: string;
16
+ name: string;
17
+ parentId: string | null;
18
+ position: Vec2;
19
+ size: Size;
20
+ type: string;
21
+ };
22
+
23
+ type SceneSnapshot = {
24
+ root: SceneNodeSnapshot;
25
+ version: number;
26
+ };
27
+
28
+ type NodeCreateSpec = {
29
+ name?: string;
30
+ parentId?: string | null;
31
+ position?: Vec2;
32
+ size?: Size;
33
+ type: string;
34
+ };
35
+
36
+ type MutationResult = {
37
+ newVersion: number;
38
+ };
39
+
40
+ type IntegrationInvokeResult = {
41
+ integrationId: string;
42
+ invokedAt: number;
43
+ operation: string;
44
+ output?: unknown;
45
+ receipt?: unknown;
46
+ status: "accepted" | "ok";
47
+ };
48
+
49
+ type LayoutAssistantContext = {
50
+ document: {
51
+ snapshot(): Promise<SceneSnapshot>;
52
+ };
53
+ integrations: {
54
+ invoke(params: {
55
+ artifacts?: Array<{ resource: unknown; role?: string }>;
56
+ input: unknown;
57
+ integrationId: string;
58
+ operation: string;
59
+ }): Promise<IntegrationInvokeResult>;
60
+ };
61
+ nodes: {
62
+ create(
63
+ specs: NodeCreateSpec[],
64
+ opts?: { expectedVersion?: number },
65
+ ): Promise<MutationResult>;
66
+ };
67
+ notify: {
68
+ send(
69
+ message: string,
70
+ options?: { kind?: "error" | "info" | "success" | "warning" },
71
+ ): Promise<void>;
72
+ };
73
+ selection: {
74
+ get(): Promise<string[]>;
75
+ };
76
+ };
77
+
78
+ type LayoutCardSuggestion = {
79
+ name: string;
80
+ position: Vec2;
81
+ size: Size;
82
+ };
83
+
84
+ type LayoutSuggestion = {
85
+ cards: LayoutCardSuggestion[];
86
+ integration: {
87
+ integrationId: string;
88
+ invokedAt: number;
89
+ operation: string;
90
+ status: string;
91
+ };
92
+ summary: string;
93
+ };
94
+
95
+ type LayoutMessage =
96
+ | {
97
+ type: "analysis";
98
+ payload: LayoutSuggestion;
99
+ }
100
+ | {
101
+ type: "applied";
102
+ payload: {
103
+ cardCount: number;
104
+ newVersion: number;
105
+ suggestion: LayoutSuggestion;
106
+ };
107
+ }
108
+ | {
109
+ type: "error";
110
+ payload: {
111
+ message: string;
112
+ };
113
+ }
114
+ | {
115
+ type: "ready";
116
+ payload: {
117
+ message: string;
118
+ };
119
+ };
120
+
121
+ definePlugin({
122
+ apiVersion: "1.0",
123
+ version: "0.1.0",
124
+ command: async (ctx) => {
125
+ const session = await ctx.ui.show({
126
+ mode: "panel",
127
+ entry: "./ui.html",
128
+ title: "AI Layout Assistant",
129
+ width: 440,
130
+ });
131
+
132
+ let closed = false;
133
+ let latestSuggestion: LayoutSuggestion | null = null;
134
+ const postToUi = async (message: LayoutMessage): Promise<void> => {
135
+ if (closed) return;
136
+ await session.postMessage(message);
137
+ };
138
+ const panelClosed = new Promise<void>((resolve) => {
139
+ session.on("close", () => {
140
+ closed = true;
141
+ void ctx.notify.send("AI Layout Assistant closed.", {
142
+ kind: "info",
143
+ });
144
+ resolve();
145
+ });
146
+ });
147
+
148
+ session.on("analyze-layout", async () => {
149
+ await handleUiAction(postToUi, async () => {
150
+ latestSuggestion = await analyzeLayout(ctx);
151
+ await postToUi({
152
+ type: "analysis",
153
+ payload: latestSuggestion,
154
+ });
155
+ });
156
+ });
157
+
158
+ session.on("apply-layout", async () => {
159
+ await handleUiAction(postToUi, async () => {
160
+ const suggestion = latestSuggestion ?? (await analyzeLayout(ctx));
161
+ latestSuggestion = suggestion;
162
+ const applied = await applyLayout(ctx, suggestion);
163
+ await postToUi({
164
+ type: "applied",
165
+ payload: {
166
+ cardCount: suggestion.cards.length,
167
+ newVersion: applied.newVersion,
168
+ suggestion,
169
+ },
170
+ });
171
+ await ctx.notify.send(
172
+ `AI Layout Assistant applied ${suggestion.cards.length} suggestion card(s).`,
173
+ { kind: "success" },
174
+ );
175
+ });
176
+ });
177
+
178
+ await postToUi({
179
+ type: "ready",
180
+ payload: {
181
+ message:
182
+ "Ready. Analyze layout through the host integration, then apply cards.",
183
+ },
184
+ });
185
+
186
+ await panelClosed;
187
+ return { closed: true, ok: true };
188
+ },
189
+ });
190
+
191
+ async function handleUiAction(
192
+ postToUi: (message: LayoutMessage) => Promise<void>,
193
+ action: () => Promise<unknown>,
194
+ ): Promise<void> {
195
+ try {
196
+ await action();
197
+ } catch (error) {
198
+ const message = error instanceof Error ? error.message : String(error);
199
+ await postToUi({ type: "error", payload: { message } });
200
+ }
201
+ }
202
+
203
+ async function analyzeLayout(
204
+ ctx: LayoutAssistantContext,
205
+ ): Promise<LayoutSuggestion> {
206
+ const [snapshot, selection] = await Promise.all([
207
+ ctx.document.snapshot(),
208
+ ctx.selection.get(),
209
+ ]);
210
+ const nodes = collectNodes(snapshot.root).filter(
211
+ (node) => node.id !== snapshot.root.id,
212
+ );
213
+ const result = await ctx.integrations.invoke({
214
+ integrationId: "ai.layout",
215
+ operation: "suggest-layout",
216
+ input: {
217
+ nodeCount: nodes.length,
218
+ selectedIds: selection,
219
+ snapshotVersion: snapshot.version,
220
+ },
221
+ });
222
+
223
+ return normalizeSuggestion(result);
224
+ }
225
+
226
+ async function applyLayout(
227
+ ctx: LayoutAssistantContext,
228
+ suggestion: LayoutSuggestion,
229
+ ): Promise<MutationResult> {
230
+ const snapshot = await ctx.document.snapshot();
231
+ return await ctx.nodes.create(
232
+ suggestion.cards.map((card) => ({
233
+ type: "shape",
234
+ name: card.name,
235
+ parentId: null,
236
+ position: card.position,
237
+ size: card.size,
238
+ })),
239
+ { expectedVersion: snapshot.version },
240
+ );
241
+ }
242
+
243
+ function normalizeSuggestion(
244
+ result: IntegrationInvokeResult,
245
+ ): LayoutSuggestion {
246
+ const output = readRecord(result.output);
247
+ const cards = readCards(output?.cards);
248
+ const fallbackCards =
249
+ cards.length > 0
250
+ ? cards
251
+ : [
252
+ {
253
+ name: "AI Layout Assistant Card A",
254
+ position: { x: 120, y: 340 },
255
+ size: { width: 120, height: 80 },
256
+ },
257
+ {
258
+ name: "AI Layout Assistant Card B",
259
+ position: { x: 260, y: 340 },
260
+ size: { width: 120, height: 80 },
261
+ },
262
+ {
263
+ name: "AI Layout Assistant Card C",
264
+ position: { x: 400, y: 340 },
265
+ size: { width: 120, height: 80 },
266
+ },
267
+ ];
268
+
269
+ return {
270
+ cards: fallbackCards,
271
+ integration: {
272
+ integrationId: result.integrationId,
273
+ invokedAt: result.invokedAt,
274
+ operation: result.operation,
275
+ status: result.status,
276
+ },
277
+ summary:
278
+ typeof output?.summary === "string"
279
+ ? output.summary
280
+ : "Host integration returned a balanced card row.",
281
+ };
282
+ }
283
+
284
+ function readCards(value: unknown): LayoutCardSuggestion[] {
285
+ if (!Array.isArray(value)) return [];
286
+ return value
287
+ .map((candidate, index) => readCard(candidate, index))
288
+ .filter((card): card is LayoutCardSuggestion => card !== null);
289
+ }
290
+
291
+ function readCard(value: unknown, index: number): LayoutCardSuggestion | null {
292
+ const record = readRecord(value);
293
+ if (!record) return null;
294
+ const position = readVec2(record.position);
295
+ const size = readSize(record.size);
296
+ if (!position || !size) return null;
297
+
298
+ return {
299
+ name:
300
+ typeof record.name === "string" && record.name.length > 0
301
+ ? record.name
302
+ : `AI Layout Assistant Card ${String.fromCharCode(65 + index)}`,
303
+ position,
304
+ size,
305
+ };
306
+ }
307
+
308
+ function readRecord(value: unknown): Record<string, unknown> | null {
309
+ if (value == null || typeof value !== "object" || Array.isArray(value)) {
310
+ return null;
311
+ }
312
+ return value as Record<string, unknown>;
313
+ }
314
+
315
+ function readVec2(value: unknown): Vec2 | null {
316
+ const record = readRecord(value);
317
+ if (!record) return null;
318
+ if (typeof record.x !== "number" || typeof record.y !== "number") {
319
+ return null;
320
+ }
321
+ return { x: record.x, y: record.y };
322
+ }
323
+
324
+ function readSize(value: unknown): Size | null {
325
+ const record = readRecord(value);
326
+ if (!record) return null;
327
+ if (
328
+ typeof record.width !== "number" ||
329
+ typeof record.height !== "number"
330
+ ) {
331
+ return null;
332
+ }
333
+ return { height: record.height, width: record.width };
334
+ }
335
+
336
+ function collectNodes(
337
+ node: SceneNodeSnapshot,
338
+ out: SceneNodeSnapshot[] = [],
339
+ ): SceneNodeSnapshot[] {
340
+ out.push(node);
341
+ for (const child of node.children) {
342
+ collectNodes(child, out);
343
+ }
344
+ return out;
345
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext", "WebWorker"],
4
+ "module": "Preserve",
5
+ "moduleResolution": "bundler",
6
+ "target": "ESNext",
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "allowImportingTsExtensions": true,
10
+ "verbatimModuleSyntax": true,
11
+ "noEmit": true
12
+ },
13
+ "include": ["src/**/*"]
14
+ }
@@ -0,0 +1,114 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>AI Layout Assistant</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light dark;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ color: CanvasText;
15
+ background: Canvas;
16
+ }
17
+
18
+ main {
19
+ display: grid;
20
+ gap: 1rem;
21
+ padding: 1rem;
22
+ }
23
+
24
+ .actions {
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ gap: 0.5rem;
28
+ }
29
+
30
+ .status {
31
+ min-height: 1.5rem;
32
+ }
33
+
34
+ pre {
35
+ max-height: 16rem;
36
+ overflow: auto;
37
+ padding: 0.75rem;
38
+ color: CanvasText;
39
+ background: Canvas;
40
+ border: 1px solid ButtonBorder;
41
+ }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <main>
46
+ <header>
47
+ <h1>AI Layout Assistant</h1>
48
+ <p>Request a host AI layout suggestion and apply it to the canvas.</p>
49
+ </header>
50
+
51
+ <div class="actions">
52
+ <button id="analyze-button" type="button">Analyze layout</button>
53
+ <button id="apply-button" type="button">Apply suggestion</button>
54
+ </div>
55
+
56
+ <p id="status" class="status" role="status">Waiting for host...</p>
57
+ <pre id="details" aria-label="Layout details"></pre>
58
+ </main>
59
+
60
+ <script>
61
+ const uiId = globalThis.__LOVISION_PLUGIN_UI_ID__;
62
+ const status = document.getElementById("status");
63
+ const details = document.getElementById("details");
64
+
65
+ function post(type, payload) {
66
+ parent.postMessage(
67
+ {
68
+ type: "plugin-ui-message",
69
+ direction: "ui-to-main",
70
+ uiId,
71
+ message: { type, payload },
72
+ },
73
+ "*",
74
+ );
75
+ }
76
+
77
+ document.getElementById("analyze-button").addEventListener("click", () => {
78
+ status.textContent = "Analyzing layout...";
79
+ details.textContent = "";
80
+ post("analyze-layout", {});
81
+ });
82
+
83
+ document.getElementById("apply-button").addEventListener("click", () => {
84
+ status.textContent = "Applying suggestion...";
85
+ post("apply-layout", {});
86
+ });
87
+
88
+ window.addEventListener("message", (event) => {
89
+ const envelope = event.data;
90
+ if (
91
+ !envelope ||
92
+ envelope.type !== "plugin-ui-message" ||
93
+ envelope.direction !== "main-to-ui" ||
94
+ envelope.uiId !== uiId
95
+ ) {
96
+ return;
97
+ }
98
+
99
+ const message = envelope.message;
100
+ if (message.type === "ready") {
101
+ status.textContent = message.payload.message;
102
+ } else if (message.type === "analysis") {
103
+ status.textContent = `Received ${message.payload.cards.length} layout suggestion card(s).`;
104
+ details.textContent = JSON.stringify(message.payload, null, 2);
105
+ } else if (message.type === "applied") {
106
+ status.textContent = `Applied ${message.payload.cardCount} layout suggestion card(s).`;
107
+ details.textContent = JSON.stringify(message.payload, null, 2);
108
+ } else if (message.type === "error") {
109
+ status.textContent = message.payload.message;
110
+ }
111
+ });
112
+ </script>
113
+ </body>
114
+ </html>
@@ -0,0 +1,24 @@
1
+ # Asset Browser
2
+
3
+ This Step 31 acceptance template inserts generated image assets through the plugin main worker. The UI sends asset intent only; the worker creates a command-scoped binary resource with `ctx.binary.create` and inserts it as an image node with `ctx.assets.insertImage`.
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Copy the printed `manifestUrl=...`, open the editor shell, then use Dev Panel -> Development -> Add by URL.
13
+
14
+ ## Use
15
+
16
+ Run **Open Asset Browser**, then click **Insert hero asset** or **Insert badge asset**. The inserted node is an image node named after the selected asset and carries plugin binary metadata in the document snapshot.
17
+
18
+ ## Formal install
19
+
20
+ ```bash
21
+ npm run build
22
+ ```
23
+
24
+ Upload the generated `dist/*.bundle.json` through Dev Panel -> Formal install, confirm the install dialog, then run the command from MainMenu.
@@ -0,0 +1,19 @@
1
+ import pluginSdkConfig from "@lovision/plugin-sdk/eslint-config";
2
+ import tsParser from "@typescript-eslint/parser";
3
+
4
+ const pluginFiles = ["src/**/*.ts"];
5
+
6
+ export default [
7
+ {
8
+ files: pluginFiles,
9
+ languageOptions: {
10
+ parser: tsParser,
11
+ ecmaVersion: "latest",
12
+ sourceType: "module",
13
+ },
14
+ },
15
+ ...pluginSdkConfig.map((entry) => ({
16
+ ...entry,
17
+ files: pluginFiles,
18
+ })),
19
+ ];
@@ -0,0 +1,29 @@
1
+ {
2
+ "id": "__PLUGIN_ID__",
3
+ "name": {
4
+ "en": "Asset Browser",
5
+ "zh-CN": "Asset Browser"
6
+ },
7
+ "version": "0.1.0",
8
+ "apiVersion": "1.0",
9
+ "editorType": ["design"],
10
+ "main": "./dist/main.js",
11
+ "ui": "./ui.html",
12
+ "documentAccess": "current-page",
13
+ "permissions": [
14
+ "asset:write",
15
+ "binary:write",
16
+ "document:read",
17
+ "notify",
18
+ "ui:panel"
19
+ ],
20
+ "commands": [
21
+ {
22
+ "id": "run",
23
+ "name": {
24
+ "en": "__COMMAND_NAME_EN__",
25
+ "zh-CN": "__COMMAND_NAME_ZH__"
26
+ }
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "plugin-dev dev",
7
+ "build": "plugin-dev build",
8
+ "validate": "plugin-dev validate",
9
+ "lint": "eslint src --max-warnings=0"
10
+ },
11
+ "devDependencies": {
12
+ "@lovision/plugin-dev": "__PLUGIN_DEV_SPEC__",
13
+ "@lovision/plugin-sdk": "__PLUGIN_SDK_SPEC__"__LOCAL_SUPPORT_DEPS__,
14
+ "@typescript-eslint/parser": "^8.30.0",
15
+ "eslint": "^9.25.1",
16
+ "typescript": "^5.8.3"
17
+ }__LOCAL_OVERRIDES__
18
+ }