@easyai-canvas/canvas-plugin-sdk 0.1.4 → 0.1.5
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.
- package/README.md +2 -0
- package/bin/easyai-plugin.mjs +1 -0
- package/dist/studio/index.html +94 -45
- package/package.json +2 -1
- package/templates/AGENTS.md +21 -0
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ npm start
|
|
|
15
15
|
|
|
16
16
|
`init --template node|action|combined` 选择首个插件模板;`--no-install` 跳过依赖安装。新项目将插件放在 `src/plugins/<目录>/`,用 `npx easyai-plugin add <目录> --template node|action|combined` 增加独立插件。每个目录拥有独立 ID、配置、UI 和 ZIP;同一插件的多个节点可继续放在该目录内。旧项目的 `src/plugin.ts`、`src/ui.ts` 与 `dist/plugin.zip` 仍可使用。
|
|
17
17
|
|
|
18
|
+
`init` 还会在项目根目录生成 `AGENTS.md`,供 Codex 等编程助手了解插件目录、SDK 边界与验证命令。项目约定变化时,请与代码一起维护该文件。
|
|
19
|
+
|
|
18
20
|
开发工作台位于 `http://localhost:4310/studio`,按插件展示配置表单和待配置状态。`npm run build` 为新结构分别输出 `dist/plugins/<pluginId>.zip` 与摘要文件;管理员可分别导入,或用 `npm start` 运行同一服务下的多个插件。
|
|
19
21
|
|
|
20
22
|
节点在 `contributes.nodes[].placement` 声明 `{ slot: 'canvas.add.node', group: 'tools', order: 100 }`,素材选择操作在 `contributes.actions[].placement` 声明 `{ slot: 'canvas.add.resource', order: 100 }`。节点分组还可选 `basic`、`storyboard`;素材入口不使用节点分组。插件的 Vue 组件负责内部样式与交互,画布负责添加入口及拖动、权限等边界。旧版 `menu` 声明继续可用;未知位置或贡献能力与位置不匹配会在打包校验时报错。
|
package/bin/easyai-plugin.mjs
CHANGED
|
@@ -138,6 +138,7 @@ async function init() {
|
|
|
138
138
|
};
|
|
139
139
|
const files = {
|
|
140
140
|
'package.json': `${JSON.stringify(project, null, 2)}\n`,
|
|
141
|
+
'AGENTS.md': await readFile(join(packageRoot, 'templates/AGENTS.md'), 'utf8'),
|
|
141
142
|
[`src/plugins/${slug}/plugin.ts`]: pluginSource(id, title, template),
|
|
142
143
|
[`src/plugins/${slug}/ui.ts`]: uiSource(template),
|
|
143
144
|
[`test/${slug}.test.ts`]: testSource(id, template, slug),
|
package/dist/studio/index.html
CHANGED
|
@@ -127,36 +127,51 @@ button:focus-visible, select:focus-visible, input:focus-visible, summary:focus-v
|
|
|
127
127
|
.empty button { justify-self: center; min-height: 40px; border: 0; border-radius: 10px; padding: 0 16px; font-size: 12px; font-weight: 600; }
|
|
128
128
|
|
|
129
129
|
/* Plugin management dialog */
|
|
130
|
-
dialog { width: min(
|
|
130
|
+
dialog { width: min(1040px, calc(100vw - 32px)); max-width: calc(100vw - 32px); max-height: min(900px, calc(100dvh - 32px)); margin: auto; overflow: hidden; border: 1px solid var(--line-strong); border-radius: 18px; padding: 0; color: var(--ink); background: #fff; box-shadow: var(--floating-shadow); }
|
|
131
131
|
dialog[open] { display: flex; flex-direction: column; }
|
|
132
132
|
dialog::backdrop { background: #30384266; }
|
|
133
|
-
.dialog-head { flex: none; display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 24px
|
|
134
|
-
.dialog-head h2 { margin: 0 0 5px; font-size:
|
|
133
|
+
.dialog-head { flex: none; display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 24px 30px 20px; border-bottom: 1px solid var(--line); }
|
|
134
|
+
.dialog-head h2 { margin: 0 0 5px; font-size: 22px; line-height: 1.35; font-weight: 650; }
|
|
135
135
|
.dialog-head p { margin: 0; color: var(--quiet); font-size: 12px; }
|
|
136
136
|
.dialog-head button { display: grid; place-items: center; width: 36px; height: 36px; flex: none; border: 0; border-radius: 9px; color: var(--quiet); background: transparent; }
|
|
137
137
|
.dialog-head button:hover { color: var(--ink); background: var(--page); }
|
|
138
138
|
.dialog-head svg { width: 18px; height: 18px; }
|
|
139
|
-
.dialog-body { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr)
|
|
140
|
-
.
|
|
141
|
-
.
|
|
142
|
-
.
|
|
139
|
+
.dialog-body { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(248px, 30%) minmax(0, 1fr); overflow: hidden; }
|
|
140
|
+
.plugin-sidebar { display: flex; flex-direction: column; min-width: 0; min-height: 0; padding: 24px 16px 20px 24px; border-right: 1px solid var(--line); background: var(--soft); }
|
|
141
|
+
.plugin-sidebar > p { margin: -6px 0 18px; color: var(--quiet); font-size: 12px; line-height: 1.5; }
|
|
142
|
+
.plugin-detail { min-width: 0; overflow: auto; padding: 27px 32px 30px; }
|
|
143
|
+
.detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
|
|
144
|
+
.detail-head h3 { margin: 0 0 5px; font-size: 20px; font-weight: 650; line-height: 1.35; }
|
|
145
|
+
.detail-head code { display: block; color: var(--quiet); font: 11px/1.5 var(--font-code); overflow-wrap: anywhere; }
|
|
146
|
+
.detail-meta { display: flex; flex-wrap: wrap; gap: 8px; margin: 17px 0 0; }
|
|
147
|
+
.detail-meta span { padding: 6px 10px; border: 1px solid var(--line); border-radius: 7px; color: var(--muted); background: var(--soft); font-size: 11px; }
|
|
148
|
+
.detail-empty { color: var(--quiet); font-size: 13px; }
|
|
149
|
+
.detail-empty[hidden], .plugin-detail-content[hidden] { display: none; }
|
|
143
150
|
.section-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 15px; }
|
|
144
151
|
.section-heading h3 { margin: 0; font-size: 13px; font-weight: 700; }
|
|
145
152
|
.status-badge { display: inline-flex; align-items: center; gap: 7px; padding: 6px 10px; border-radius: 999px; color: var(--warning); background: #faf1df; font-size: 11px; font-weight: 600; white-space: nowrap; }
|
|
146
153
|
.status-badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
|
|
147
154
|
.status-badge.ready { color: var(--success); background: #e9f4ef; }
|
|
148
155
|
.status-badge.error { color: var(--danger); background: #fff1ef; }
|
|
149
|
-
.plugin-list { display: grid; gap: 8px;
|
|
156
|
+
.plugin-list { display: grid; align-content: start; gap: 8px; min-height: 0; overflow: auto; padding-right: 7px; }
|
|
150
157
|
.plugin-list:empty::before { content: "暂无可调试的插件"; padding: 18px; border: 1px dashed var(--line-strong); border-radius: 12px; color: var(--quiet); font-size: 12px; text-align: center; }
|
|
151
|
-
.
|
|
158
|
+
.configuration-section { margin-top: 28px; padding-top: 22px; border-top: 1px solid var(--line); }
|
|
159
|
+
.configuration-section h4 { margin: 0; font-size: 15px; font-weight: 650; }
|
|
160
|
+
.configuration-section > p { margin: 7px 0 16px; color: var(--quiet); font-size: 12px; line-height: 1.5; }
|
|
161
|
+
.no-config { padding: 16px; border: 1px solid var(--line); border-radius: 10px; color: var(--muted); background: var(--soft); font-size: 12px; }
|
|
162
|
+
.no-config[hidden] { display: none; }
|
|
163
|
+
.plugin-config { display: grid; gap: 14px; padding: 18px; border: 1px solid var(--line); border-radius: 12px; background: var(--soft); }
|
|
152
164
|
.plugin-config[hidden] { display: none; }
|
|
153
|
-
.plugin-config
|
|
154
|
-
.plugin-config
|
|
155
|
-
.plugin-config input
|
|
156
|
-
.plugin-config button { min-height:
|
|
157
|
-
.plugin-config .config-actions { display: flex; gap: 8px; }
|
|
165
|
+
.plugin-config label { display: grid; gap: 7px; color: var(--muted); font-size: 12px; font-weight: 600; }
|
|
166
|
+
.plugin-config input:not([type="checkbox"]), .plugin-config select { width: 100%; min-height: 42px; padding: 8px 11px; border: 1px solid var(--line-strong); border-radius: 9px; color: var(--ink); background: #fff; font-weight: 400; }
|
|
167
|
+
.plugin-config input[type="checkbox"] { width: 18px; height: 18px; }
|
|
168
|
+
.plugin-config button { min-height: 40px; padding: 0 14px; border: 1px solid var(--line-strong); border-radius: 9px; color: var(--ink); background: #fff; font-size: 12px; font-weight: 600; }
|
|
169
|
+
.plugin-config .config-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
170
|
+
.plugin-config .config-actions button[type="submit"] { border-color: var(--accent); color: #fff; background: var(--accent); }
|
|
171
|
+
.plugin-config #config-status { margin: 0; color: var(--quiet); font-size: 12px; line-height: 1.5; }
|
|
172
|
+
#config-fields { display: grid; gap: 14px; }
|
|
158
173
|
.plugin-config small { color: var(--quiet); }
|
|
159
|
-
.plugin-choice { position: relative; display: grid; grid-template-columns: 18px minmax(0, 1fr)
|
|
174
|
+
.plugin-choice { position: relative; display: grid; grid-template-columns: 18px minmax(0, 1fr); align-items: center; gap: 10px; min-height: 78px; border: 1px solid var(--line); border-radius: 12px; padding: 11px 12px; background: #fff; cursor: pointer; }
|
|
160
175
|
.plugin-choice:hover { border-color: var(--line-strong); background: var(--soft); }
|
|
161
176
|
.plugin-choice:has(input:checked) { border-color: #aab6c1; background: #f9fafb; }
|
|
162
177
|
.plugin-choice:focus-within { outline: 2px solid #8e9ca9; outline-offset: -3px; }
|
|
@@ -167,16 +182,25 @@ dialog::backdrop { background: #30384266; }
|
|
|
167
182
|
.plugin-choice-copy strong, .plugin-choice-copy code { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
168
183
|
.plugin-choice-copy strong { color: var(--ink); font-size: 13px; font-weight: 600; }
|
|
169
184
|
.plugin-choice-copy code { color: var(--quiet); font: 400 10px/1.4 var(--font-code); }
|
|
170
|
-
.plugin-choice-meta { color: var(--quiet); font-size: 11px; white-space: nowrap; }
|
|
185
|
+
.plugin-choice-meta { grid-column: 2; color: var(--quiet); font-size: 11px; white-space: nowrap; }
|
|
171
186
|
.plugin-choice-meta.disabled { color: var(--danger); }
|
|
172
|
-
.plugin-actions { display: flex; align-items: center; gap: 8px; margin-top:
|
|
173
|
-
.plugin-actions button { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height:
|
|
187
|
+
.plugin-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 22px; }
|
|
188
|
+
.plugin-actions button { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height: 42px; border: 1px solid var(--line-strong); border-radius: 10px; padding: 0 14px; color: var(--ink); background: #fff; box-shadow: var(--control-shadow); font-size: 12px; font-weight: 600; }
|
|
174
189
|
.plugin-actions button:hover:not(:disabled) { background: var(--page); }
|
|
175
|
-
.plugin-actions #open-plugin {
|
|
190
|
+
.plugin-actions #open-plugin { border-color: var(--accent); color: #fff; background: var(--accent); }
|
|
191
|
+
.plugin-actions #open-plugin:hover:not(:disabled) { background: #252b31; }
|
|
176
192
|
.plugin-actions svg { width: 16px; height: 16px; flex: none; }
|
|
177
193
|
.package-note { margin: 10px 0 0; color: var(--quiet); font-size: 11px; line-height: 1.5; }
|
|
178
194
|
.package-note:empty { display: none; }
|
|
179
195
|
.package-note.error { color: var(--danger); }
|
|
196
|
+
.secondary-panels { display: grid; gap: 12px; margin-top: 26px; }
|
|
197
|
+
.secondary-panels > details { border: 1px solid var(--line); border-radius: 11px; background: #fff; }
|
|
198
|
+
.secondary-panels > details > summary { display: flex; align-items: center; gap: 8px; padding: 14px 16px; font-size: 12px; font-weight: 650; cursor: pointer; list-style: none; }
|
|
199
|
+
.secondary-panels > details > summary::-webkit-details-marker { display: none; }
|
|
200
|
+
.secondary-panels > details > summary::after { content: "+"; margin-left: auto; color: var(--quiet); font-size: 18px; font-weight: 400; line-height: 1; }
|
|
201
|
+
.secondary-panels > details[open] > summary::after { content: "−"; }
|
|
202
|
+
.secondary-panels .panel-content { padding: 0 16px 16px; }
|
|
203
|
+
.secondary-panels .activity-list { max-height: 220px; }
|
|
180
204
|
.section-context { overflow: hidden; color: var(--quiet); font-size: 11px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
|
|
181
205
|
.session-summary { margin: 0; }
|
|
182
206
|
.session-summary > div { display: flex; align-items: center; justify-content: space-between; gap: 14px; min-height: 51px; border-top: 1px solid #e9ebea; }
|
|
@@ -188,10 +212,8 @@ dialog::backdrop { background: #30384266; }
|
|
|
188
212
|
.technical-details dl { display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: 10px 12px; margin: 14px 0 0; padding: 14px; border: 1px solid var(--line); border-radius: 12px; background: var(--soft); font-size: 11px; }
|
|
189
213
|
.technical-details dt { color: var(--quiet); }
|
|
190
214
|
.technical-details dd { margin: 0; overflow-wrap: anywhere; font-family: var(--font-code); }
|
|
191
|
-
.activity-section { display: flex; flex-direction: column; min-width: 0; overflow: hidden; padding: 24px 18px; border-left: 1px solid var(--line); background: var(--soft); }
|
|
192
|
-
.activity-section > p { margin: -6px 0 16px; color: var(--quiet); font-size: 12px; line-height: 1.5; }
|
|
193
215
|
.activity-count { color: var(--quiet); font-size: 11px; }
|
|
194
|
-
.activity-list {
|
|
216
|
+
.activity-list { min-height: 80px; overflow: auto; border-top: 1px solid var(--line); }
|
|
195
217
|
.activity-list:empty::before { content: "暂无活动"; display: block; padding: 25px; color: var(--quiet); font-size: 12px; text-align: center; }
|
|
196
218
|
.activity-item { display: grid; grid-template-columns: 58px minmax(0, 1fr); gap: 10px; padding: 11px 2px; color: var(--muted); font-size: 12px; line-height: 1.5; }
|
|
197
219
|
.activity-item + .activity-item { border-top: 1px solid #eceeed; }
|
|
@@ -211,10 +233,13 @@ dialog::backdrop { background: #30384266; }
|
|
|
211
233
|
.diagnostics { padding: 32px 46px 38px; border-top: 1px solid var(--line); border-left: 0; }
|
|
212
234
|
}
|
|
213
235
|
@media (max-width: 760px) {
|
|
214
|
-
.dialog-body {
|
|
215
|
-
.
|
|
216
|
-
.
|
|
217
|
-
.
|
|
236
|
+
.dialog-body { display: block; overflow: auto; }
|
|
237
|
+
.plugin-sidebar { padding: 18px 20px; border-right: 0; border-bottom: 1px solid var(--line); }
|
|
238
|
+
.plugin-sidebar > p { margin-bottom: 12px; }
|
|
239
|
+
.plugin-list { display: flex; flex: none; overflow-x: auto; padding: 2px 0 8px; }
|
|
240
|
+
.plugin-choice { flex: 0 0 min(245px, 75vw); }
|
|
241
|
+
.plugin-detail { overflow: visible; padding: 24px 20px; }
|
|
242
|
+
.detail-head { position: sticky; top: 0; z-index: 1; margin: -24px -20px 0; padding: 14px 20px; border-bottom: 1px solid var(--line); background: #fff; }
|
|
218
243
|
}
|
|
219
244
|
@media (max-width: 520px) {
|
|
220
245
|
dialog { height: min(760px, calc(100dvh - 32px)); }
|
|
@@ -238,21 +263,17 @@ dialog::backdrop { background: #30384266; }
|
|
|
238
263
|
.controls .button-label { display: none; }
|
|
239
264
|
.dialog-head { padding: 18px 20px; }
|
|
240
265
|
.dialog-head button { width: 44px; height: 44px; margin: -6px -6px 0 0; }
|
|
241
|
-
.
|
|
242
|
-
.target-section, .session-section { padding-block: 18px; }
|
|
266
|
+
.detail-head h3 { font-size: 18px; }
|
|
243
267
|
.section-heading { margin-bottom: 12px; }
|
|
244
268
|
.session-summary > div { min-height: 42px; }
|
|
245
269
|
.technical-details { padding-top: 12px; }
|
|
246
|
-
.activity-section { padding: 18px 20px; }
|
|
247
270
|
.dialog-actions { padding: 12px 20px; }
|
|
248
271
|
.dialog-actions button { min-height: 44px; padding-inline: 9px; }
|
|
249
272
|
.dock button { min-height: 44px; padding-inline: 0; }
|
|
250
273
|
.dock-grip, .dock-collapse { width: 44px; }
|
|
251
274
|
}
|
|
252
275
|
@media (max-width: 375px) {
|
|
253
|
-
.plugin-choice { grid-template-columns: 18px minmax(0, 1fr); }
|
|
254
276
|
.plugin-choice-copy code { white-space: normal; overflow-wrap: anywhere; }
|
|
255
|
-
.plugin-choice-meta { grid-column: 2; }
|
|
256
277
|
.dialog-actions { flex-wrap: wrap; }
|
|
257
278
|
.dialog-actions > div { margin-left: auto; }
|
|
258
279
|
.dialog-actions button { padding-inline: 8px; }
|
|
@@ -295,10 +316,17 @@ dialog::backdrop { background: #30384266; }
|
|
|
295
316
|
</div>
|
|
296
317
|
<div id="notice" class="notice" role="status" hidden></div>
|
|
297
318
|
<dialog id="panel" aria-labelledby="panel-title">
|
|
298
|
-
<div class="dialog-head"><div><h2 id="panel-title">插件管理</h2><p
|
|
299
|
-
<div class="dialog-body"
|
|
300
|
-
<
|
|
301
|
-
<
|
|
319
|
+
<div class="dialog-head"><div><h2 id="panel-title">插件管理</h2><p>选择插件,查看配置并进入画布调试</p></div><button id="close" type="button" aria-label="关闭管理面板"><svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M4.5 4.5 15.5 15.5M15.5 4.5 4.5 15.5"/></svg></button></div>
|
|
320
|
+
<div class="dialog-body">
|
|
321
|
+
<aside class="plugin-sidebar" aria-labelledby="target-title"><div class="section-heading"><h3 id="target-title">本地插件</h3><span id="plugin-count" class="activity-count">0 个</span></div><p>选择插件后在右侧管理</p><div id="plugin-list" class="plugin-list" role="radiogroup" aria-labelledby="target-title"></div></aside>
|
|
322
|
+
<div class="plugin-detail"><p id="detail-empty" class="detail-empty" hidden>暂无插件。请在本地服务中注册插件。</p><div id="plugin-detail-content" class="plugin-detail-content">
|
|
323
|
+
<div class="detail-head"><div><h3 id="detail-name">插件</h3><code id="detail-id"></code></div><span id="detail-status" class="status-badge" role="status">检查中</span></div>
|
|
324
|
+
<div class="detail-meta"><span id="detail-nodes">0 个节点</span><span id="detail-actions">0 个操作</span><span id="detail-package">插件包不可用</span></div>
|
|
325
|
+
<div class="plugin-actions"><button id="open-plugin" type="button"><svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10h11m-4-4 4 4-4 4M16 4h2v12h-2"/></svg><span>在画布中调试</span></button><button id="download-package" type="button" disabled><svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 2.5v10m-3.5-3.5L10 12.5 13.5 9M3.5 14v2.5h13V14"/></svg><span id="download-label">下载插件包</span></button></div><p id="package-note" class="package-note" role="status" aria-live="polite"></p>
|
|
326
|
+
<div class="configuration-section" aria-labelledby="configuration-title"><h4 id="configuration-title">插件配置</h4><p>配置仅保存在本地开发服务中。</p><div id="no-config" class="no-config" hidden>此插件无需配置,可以直接调试或下载插件包。</div><form id="plugin-config" class="plugin-config" hidden><p id="config-status" role="status" aria-live="polite"></p><div id="config-fields"></div><div class="config-actions"><button type="submit">保存并启用</button><button id="config-check" type="button">检查配置</button></div></form></div>
|
|
327
|
+
<div class="secondary-panels"><details><summary>连接与构建 <span id="panel-status" class="status-badge" role="status">正在连接</span></summary><div class="panel-content"><dl class="session-summary"><div><dt>开发服务</dt><dd id="connection">—</dd></div><div><dt>当前构建</dt><dd id="build">—</dd></div><div><dt>调试画布</dt><dd id="project">—</dd></div></dl><span id="active-plugin" class="section-context" hidden></span><div class="sdk-card" hidden><strong>画布插件 SDK</strong><p class="sdk-message" role="status" aria-live="polite"></p><button class="sdk-action" type="button" hidden>升级 SDK</button></div><details class="technical-details"><summary>查看连接详情</summary><dl><dt>连接 ID</dt><dd id="connection-id">—</dd><dt>构建摘要</dt><dd id="build-full">—</dd><dt>项目 ID</dt><dd id="project-id">—</dd></dl></details></div></details><details><summary>最近活动 <span id="activity-count" class="activity-count">0 条</span></summary><div class="panel-content"><div id="log" class="activity-list" role="log" aria-label="调试活动" aria-live="polite"></div></div></details></div>
|
|
328
|
+
</div></div>
|
|
329
|
+
</div>
|
|
302
330
|
<div class="dialog-actions"><button id="disconnect-panel" type="button">退出连接</button><div><button id="panel-cancel" type="button">关闭</button><button id="reconnect" class="primary" type="button"><svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16.6 8.5A6.7 6.7 0 1 0 17 12M16.5 3.5v5h-5"/></svg><span>重新连接</span></button></div></div>
|
|
303
331
|
</dialog>
|
|
304
332
|
<script>
|
|
@@ -507,17 +535,19 @@ dialog::backdrop { background: #30384266; }
|
|
|
507
535
|
$('dock-expand').addEventListener('click', () => { manuallyCollapsed = false; expandedOverDialog = canvasDialogOpen; renderDock(); });
|
|
508
536
|
const chosen = () => catalog?.plugins.find(item => item.id === selected);
|
|
509
537
|
const panelPlugin = () => catalog?.plugins.find(item => item.id === panelSelected);
|
|
510
|
-
let configFormKey = '';
|
|
538
|
+
let configFormKey = '', configFeedback = null;
|
|
511
539
|
function renderConfig() {
|
|
512
540
|
const plugin = panelPlugin();
|
|
513
541
|
const form = $('plugin-config');
|
|
514
|
-
const fields = plugin?.manifest
|
|
542
|
+
const fields = plugin?.manifest?.configFields || [];
|
|
515
543
|
form.hidden = !fields.length;
|
|
544
|
+
$('no-config').hidden = !plugin || Boolean(fields.length);
|
|
516
545
|
if (!fields.length) { configFormKey = ''; return; }
|
|
517
546
|
const key = JSON.stringify([plugin.id, plugin.buildKey, plugin.config, plugin.secretConfigured]);
|
|
518
547
|
if (key === configFormKey) return;
|
|
519
548
|
configFormKey = key;
|
|
520
|
-
$('config-fields').replaceChildren();
|
|
549
|
+
$('config-fields').replaceChildren();
|
|
550
|
+
$('config-status').textContent = configFeedback?.pluginId === plugin.id ? configFeedback.message : plugin.configurationRequired ? '待配置:填写必填项后才能启用。' : '可以修改配置并保存。';
|
|
521
551
|
for (const field of fields) {
|
|
522
552
|
const label = document.createElement('label');
|
|
523
553
|
label.textContent = `${field.label}${field.required ? ' *' : ''}`;
|
|
@@ -544,6 +574,7 @@ dialog::backdrop { background: #30384266; }
|
|
|
544
574
|
}
|
|
545
575
|
function renderPluginList() {
|
|
546
576
|
const plugins = catalog?.plugins || [];
|
|
577
|
+
$('plugin-count').textContent = `${plugins.length} 个`;
|
|
547
578
|
const key = JSON.stringify(plugins.map(item => [item.id, item.name, item.nodes, item.actions, item.enabled, item.configurationRequired]));
|
|
548
579
|
if (key !== pluginListKey) {
|
|
549
580
|
pluginListKey = key;
|
|
@@ -593,6 +624,15 @@ dialog::backdrop { background: #30384266; }
|
|
|
593
624
|
renderPluginList();
|
|
594
625
|
renderConfig();
|
|
595
626
|
const inspected = panelPlugin();
|
|
627
|
+
$('detail-empty').hidden = Boolean(inspected);
|
|
628
|
+
$('plugin-detail-content').hidden = !inspected;
|
|
629
|
+
$('detail-name').textContent = inspected?.name || '插件';
|
|
630
|
+
$('detail-id').textContent = inspected?.id || '';
|
|
631
|
+
$('detail-nodes').textContent = `${inspected?.nodes || 0} 个节点`;
|
|
632
|
+
$('detail-actions').textContent = `${inspected?.actions || 0} 个操作`;
|
|
633
|
+
$('detail-package').textContent = inspected?.package ? '插件包可下载' : '插件包不可用';
|
|
634
|
+
$('detail-status').textContent = inspected?.configurationRequired ? '待配置' : inspected?.enabled ? '已启用' : '已停用';
|
|
635
|
+
$('detail-status').className = `status-badge ${inspected?.configurationRequired ? '' : inspected?.enabled ? 'ready' : 'error'}`;
|
|
596
636
|
$('open-plugin').disabled = !inspected?.enabled || busy || packageDownloading;
|
|
597
637
|
$('download-package').disabled = !inspected?.package || packageDownloading;
|
|
598
638
|
$('download-package').setAttribute('aria-busy', String(packageDownloading));
|
|
@@ -703,14 +743,21 @@ dialog::backdrop { background: #30384266; }
|
|
|
703
743
|
frame.contentWindow.postMessage({ protocol, type: 'refresh', sessionId: session.sessionId, pluginId: selected, requestId: waiting }, catalog.platformOrigin);
|
|
704
744
|
render();
|
|
705
745
|
});
|
|
706
|
-
|
|
707
|
-
|
|
746
|
+
function openPanel(forConfiguration = false) {
|
|
747
|
+
panelSelected = forConfiguration ? catalog?.plugins.find(item => item.configurationRequired)?.id || selected : selected;
|
|
748
|
+
packageMessage = ''; packageError = false; configFeedback = null; configFormKey = ''; render(); panel.showModal();
|
|
749
|
+
const firstField = forConfiguration ? $('config-fields').querySelector('input, select') : null;
|
|
750
|
+
(firstField || $('plugin-list').querySelector('input:checked') || $('close')).focus();
|
|
751
|
+
renderDock();
|
|
752
|
+
}
|
|
753
|
+
$('manage').addEventListener('click', () => openPanel());
|
|
754
|
+
$('open-local-config').addEventListener('click', () => openPanel(true));
|
|
708
755
|
panel.addEventListener('close', () => { renderDock(); ($('setup').hidden ? $('manage') : $('open-local-config')).focus(); });
|
|
709
756
|
$('close').addEventListener('click', () => panel.close());
|
|
710
757
|
$('panel-cancel').addEventListener('click', () => panel.close());
|
|
711
758
|
$('plugin-list').addEventListener('change', event => {
|
|
712
759
|
if (!event.target.matches('input[name="studio-plugin"]')) return;
|
|
713
|
-
panelSelected = event.target.value; packageMessage = ''; packageError = false; render();
|
|
760
|
+
panelSelected = event.target.value; packageMessage = ''; packageError = false; configFeedback = null; render(); document.querySelector('.plugin-detail').scrollTop = 0;
|
|
714
761
|
});
|
|
715
762
|
$('plugin-config').addEventListener('submit', async event => {
|
|
716
763
|
event.preventDefault();
|
|
@@ -726,9 +773,10 @@ dialog::backdrop { background: #30384266; }
|
|
|
726
773
|
const response = await fetch(`/studio/plugins/${encodeURIComponent(plugin.id)}/configure`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ config, enabled: true }) });
|
|
727
774
|
const result = await response.json();
|
|
728
775
|
if (!response.ok) throw new Error(result.error || '配置保存失败');
|
|
729
|
-
|
|
776
|
+
configFeedback = { pluginId: plugin.id, message: '配置已保存,插件已启用。' };
|
|
777
|
+
$('config-status').textContent = configFeedback.message;
|
|
730
778
|
await loadCatalog();
|
|
731
|
-
} catch (error) { $('config-status').textContent =
|
|
779
|
+
} catch (error) { configFeedback = { pluginId: plugin.id, message: error.message || '配置保存失败' }; $('config-status').textContent = configFeedback.message; }
|
|
732
780
|
});
|
|
733
781
|
$('config-check').addEventListener('click', async () => {
|
|
734
782
|
const plugin = panelPlugin(); if (!plugin) return;
|
|
@@ -736,8 +784,9 @@ dialog::backdrop { background: #30384266; }
|
|
|
736
784
|
const response = await fetch(`/studio/plugins/${encodeURIComponent(plugin.id)}/check`, { method: 'POST' });
|
|
737
785
|
const result = await response.json();
|
|
738
786
|
if (!response.ok) throw new Error(result.error || '配置检查失败');
|
|
739
|
-
|
|
740
|
-
|
|
787
|
+
configFeedback = { pluginId: plugin.id, message: result.message || result.result?.message || '配置检查通过。' };
|
|
788
|
+
$('config-status').textContent = configFeedback.message;
|
|
789
|
+
} catch (error) { configFeedback = { pluginId: plugin.id, message: error.message || '配置检查失败' }; $('config-status').textContent = configFeedback.message; }
|
|
741
790
|
});
|
|
742
791
|
$('open-plugin').addEventListener('click', () => {
|
|
743
792
|
if (!panelPlugin()?.enabled || busy) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easyai-canvas/canvas-plugin-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "EasyAI 画布插件开发工具与运行时",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"bin",
|
|
28
28
|
"dist",
|
|
29
29
|
"tools",
|
|
30
|
+
"templates",
|
|
30
31
|
"README.md",
|
|
31
32
|
"LICENSE"
|
|
32
33
|
],
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# 项目开发指引
|
|
2
|
+
|
|
3
|
+
本项目使用 `@easyai-canvas/canvas-plugin-sdk` 开发 EasyAI 画布插件。请以当前安装版本的类型定义和项目代码为准;项目使用 Node 22.22.0 或更新的 Node 22 版本。
|
|
4
|
+
|
|
5
|
+
## 代码位置
|
|
6
|
+
|
|
7
|
+
- 每个独立插件放在 `src/plugins/<目录>/`,其中 `plugin.ts` 声明 manifest、节点或操作及服务端处理器,`ui.ts` 实现 Vue 界面。对应契约测试放在 `test/`。
|
|
8
|
+
- 新增独立插件时运行 `npx easyai-plugin add <目录> --template node|action|combined`。同一插件的多个节点继续放在同一个目录,并保持插件 ID 与节点类型唯一。
|
|
9
|
+
- 只通过 SDK 的公开导入路径使用能力:`@easyai-canvas/canvas-plugin-sdk`、`/protocol`、`/ui`。不要依赖 EasyAI 平台源码或 SDK 内部文件。
|
|
10
|
+
|
|
11
|
+
## 开发约定
|
|
12
|
+
|
|
13
|
+
- 修改 manifest、参数、输入输出或处理器时,同步更新对应契约测试。服务端从 `request.parameters` 和 `request.inputsByPort` 读取业务输入,并返回符合协议的输出。
|
|
14
|
+
- 前端通过 `usePluginNode()` 或 `usePluginAction()` 与画布交互。业务密钥放在插件配置或部署环境中,不写入 `ui.ts` 或前端产物。
|
|
15
|
+
- 有外部副作用的处理器使用 `executionId` 保证业务幂等;只有确认可安全重试时才声明 `safeRetry`。
|
|
16
|
+
|
|
17
|
+
## 验证与交付
|
|
18
|
+
|
|
19
|
+
- 开发预览:`npm run dev`,工作台位于 `http://localhost:4310/studio`。
|
|
20
|
+
- 修改插件后运行 `npm run test:contract`;交付前运行 `npm run build`,检查 `dist/plugins/<pluginId>.zip` 与 `.sha256`。
|
|
21
|
+
- ZIP 由平台管理员审核、导入并启用;独立服务部署的配置参见 SDK README 和项目 README。
|