@atomm-developer/generator-workbench 0.1.0 → 0.1.1

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/dist/index.es.js CHANGED
@@ -21,6 +21,22 @@ function createAuthController(args) {
21
21
  }
22
22
  function createExportController(args) {
23
23
  const { sdk, runtime, config, element } = args;
24
+ async function ensureLoggedIn() {
25
+ if (sdk.auth.getStatus().isLogin) {
26
+ return;
27
+ }
28
+ await sdk.auth.login();
29
+ if (!sdk.auth.getStatus().isLogin) {
30
+ throw new Error("[generator-workbench] login is required before export");
31
+ }
32
+ }
33
+ async function consumeBilling() {
34
+ var _a;
35
+ if (!((_a = sdk.billing) == null ? void 0 : _a.consume)) {
36
+ return;
37
+ }
38
+ await sdk.billing.consume();
39
+ }
24
40
  return {
25
41
  async exportSvg() {
26
42
  var _a;
@@ -29,6 +45,8 @@ function createExportController(args) {
29
45
  runtime,
30
46
  element
31
47
  }));
48
+ await ensureLoggedIn();
49
+ await consumeBilling();
32
50
  return sdk.export.download({ format: "svg" });
33
51
  },
34
52
  async openInStudio() {
@@ -38,6 +56,8 @@ function createExportController(args) {
38
56
  runtime,
39
57
  element
40
58
  }));
59
+ await ensureLoggedIn();
60
+ await consumeBilling();
41
61
  return sdk.export.openInStudio({ format: "svg" });
42
62
  }
43
63
  };
@@ -151,6 +171,8 @@ function createInitialWorkbenchState() {
151
171
  isLogin: false,
152
172
  userInfo: null
153
173
  },
174
+ creditsBalance: 0,
175
+ billingUsage: null,
154
176
  busy: {
155
177
  login: false,
156
178
  importTemplate: false,
@@ -185,264 +207,693 @@ function queryRequired(root, selector) {
185
207
  function collectWorkbenchRefs(root) {
186
208
  return {
187
209
  root,
188
- topbar: queryRequired(root, ".topbar"),
189
210
  workspace: queryRequired(root, '[data-role="workspace"]'),
190
211
  logoArea: queryRequired(root, '[data-role="logo-area"]'),
191
- importTemplateBtn: queryRequired(root, '[data-role="import-template"]'),
192
- exportTemplateBtn: queryRequired(root, '[data-role="export-template"]'),
193
- loginBtn: queryRequired(root, '[data-role="login"]'),
194
- avatarButton: queryRequired(root, '[data-role="avatar-button"]'),
195
- avatarImage: queryRequired(root, '[data-role="avatar-image"]'),
196
- avatarMenu: queryRequired(root, '[data-role="avatar-menu"]'),
197
- logoutBtn: queryRequired(root, '[data-role="logout"]'),
198
- panelSidebar: queryRequired(root, '[data-role="panel-sidebar"]'),
199
- panelHost: queryRequired(root, '[data-role="panel-host"]'),
200
- panelActions: queryRequired(root, '[data-role="panel-actions"]'),
201
212
  canvasHost: queryRequired(root, '[data-role="canvas-host"]'),
202
- fabTrigger: queryRequired(root, '[data-role="fab-trigger"]'),
203
- fabMenu: queryRequired(root, '[data-role="fab-menu"]'),
204
- exportSvgBtn: queryRequired(root, '[data-role="export-svg"]'),
205
- openInStudioBtn: queryRequired(root, '[data-role="open-in-studio"]'),
213
+ panelHost: queryRequired(root, '[data-role="panel-host"]'),
206
214
  templateFileInput: queryRequired(root, '[data-role="template-file-input"]')
207
215
  };
208
216
  }
209
- function applyWorkbenchConfig(refs, config) {
210
- refs.logoArea.textContent = config.logoText || config.title;
211
- refs.workspace.classList.toggle("panel-left", config.panelTarget === "left");
212
- refs.workspace.classList.toggle("panel-right", config.panelTarget !== "left");
213
- refs.importTemplateBtn.hidden = config.templateEnabled === false;
214
- refs.exportTemplateBtn.hidden = config.templateEnabled === false;
215
- refs.exportSvgBtn.hidden = config.exportEnabled === false;
216
- refs.openInStudioBtn.hidden = config.studioEnabled === false;
217
- }
218
- const WORKBENCH_TEMPLATE = `
219
- <style>
220
- :host {
221
- display: block;
222
- min-height: 100%;
223
- color: #111827;
224
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
225
- }
217
+ const ATOMM_UI_CDN_CSS_URL = "https://minio-download.makeblock.com/resource/atomm-ui-umd/dist-browser/atomm-ui.min.css";
218
+ const CREDIT_ICON_DATA_URI = "data:image/svg+xml,%3csvg%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3ccircle%20cx='12'%20cy='12'%20r='9'%20fill='url(%23paint0_linear_503_69253)'/%3e%3cg%20filter='url(%23filter0_d_503_69253)'%3e%3crect%20x='7.75781'%20y='12'%20width='6'%20height='6'%20rx='1'%20transform='rotate(-45%207.75781%2012)'%20fill='url(%23paint1_linear_503_69253)'/%3e%3c/g%3e%3cdefs%3e%3cfilter%20id='filter0_d_503_69253'%20x='2.17188'%20y='4.17188'%20width='19.6572'%20height='19.6562'%20filterUnits='userSpaceOnUse'%20color-interpolation-filters='sRGB'%3e%3cfeFlood%20flood-opacity='0'%20result='BackgroundImageFix'/%3e%3cfeColorMatrix%20in='SourceAlpha'%20type='matrix'%20values='0%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200%200%20127%200'%20result='hardAlpha'/%3e%3cfeOffset%20dy='2'/%3e%3cfeGaussianBlur%20stdDeviation='3'/%3e%3cfeComposite%20in2='hardAlpha'%20operator='out'/%3e%3cfeColorMatrix%20type='matrix'%20values='0%200%200%200%201%200%200%200%200%200.206957%200%200%200%200%200.0670085%200%200%200%201%200'/%3e%3cfeBlend%20mode='normal'%20in2='BackgroundImageFix'%20result='effect1_dropShadow_503_69253'/%3e%3cfeBlend%20mode='normal'%20in='SourceGraphic'%20in2='effect1_dropShadow_503_69253'%20result='shape'/%3e%3c/filter%3e%3clinearGradient%20id='paint0_linear_503_69253'%20x1='12'%20y1='3'%20x2='12'%20y2='21'%20gradientUnits='userSpaceOnUse'%3e%3cstop%20stop-color='%23FFA346'/%3e%3cstop%20offset='1'%20stop-color='%23FF7C23'/%3e%3c/linearGradient%3e%3clinearGradient%20id='paint1_linear_503_69253'%20x1='13.2327'%20y1='12.5252'%20x2='8.63652'%20y2='18.5356'%20gradientUnits='userSpaceOnUse'%3e%3cstop%20stop-color='white'/%3e%3cstop%20offset='1'%20stop-color='%23FFD3B5'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e";
219
+ const EXPORT_ICON_DATA_URI = "data:image/svg+xml,%3Csvg%20viewBox%3D'0%200%201088%201024'%20xmlns%3D'http%3A//www.w3.org/2000/svg'%3E%3Cpath%20d%3D'M416%20128v85.312h-128A42.688%2042.688%200%200%200%20245.312%20256v512c0%2023.552%2019.136%2042.688%2042.688%2042.688h512a42.688%2042.688%200%200%200%2042.688-42.688V597.312H928V768a128%20128%200%200%201-128%20128h-512a128%20128%200%200%201-128-128V256a128%20128%200%200%201%20128-128h128z'%20fill%3D'%23ffffff'/%3E%3Cpath%20d%3D'M723.52%20520.832L924.352%20320l-200.832-200.832-60.352%2060.352%2097.856%2097.792h-67.712A298.688%20298.688%200%200%200%20394.688%20576v85.312H480V576a213.312%20213.312%200%200%201%20213.312-213.312h67.712l-97.856%2097.792%2060.352%2060.352z'%20fill%3D'%23ffffff'/%3E%3C/svg%3E";
220
+ const WORKBENCH_SHELL_STYLES = `
221
+ :host {
222
+ display: block;
223
+ height: 100%;
224
+ color: #111827;
225
+ font-family: Inter, "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
226
+ --bg-soft: #f3f4f6;
227
+ --border-default: #e5e7eb;
228
+ --text-primary: #111827;
229
+ --text-secondary: #4b5563;
230
+ --text-muted: #9ca3af;
231
+ }
226
232
 
227
- * {
228
- box-sizing: border-box;
229
- }
233
+ * {
234
+ box-sizing: border-box;
235
+ }
230
236
 
231
- .shell {
232
- min-height: 100%;
233
- display: flex;
234
- flex-direction: column;
235
- background: #f5f7fb;
236
- }
237
+ [data-workbench-vue-root] {
238
+ height: 100%;
239
+ }
237
240
 
238
- .topbar {
239
- height: 56px;
240
- display: flex;
241
- align-items: center;
242
- justify-content: space-between;
243
- gap: 16px;
244
- padding: 0 16px;
245
- background: #fff;
246
- border-bottom: 1px solid #e5e7eb;
247
- }
241
+ .shell {
242
+ height: 100%;
243
+ display: flex;
244
+ flex-direction: column;
245
+ background:
246
+ radial-gradient(rgba(17, 24, 39, 0.06) 1px, transparent 1px),
247
+ linear-gradient(180deg, rgba(255, 255, 255, 0.7), rgba(249, 250, 251, 0.96));
248
+ background-size: 20px 20px, auto;
249
+ }
248
250
 
249
- .logo-area {
250
- font-weight: 600;
251
- }
251
+ .topbar,
252
+ .app-topbar {
253
+ height: 64px;
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: space-between;
257
+ gap: 12px;
258
+ padding: 0 24px;
259
+ background: rgba(255, 255, 255, 0.92);
260
+ border-bottom: 1px solid var(--border-default);
261
+ backdrop-filter: blur(12px);
262
+ position: relative;
263
+ z-index: 20;
264
+ }
252
265
 
253
- .topbar-actions {
254
- display: flex;
255
- align-items: center;
256
- gap: 8px;
257
- }
266
+ .logo-area,
267
+ .app-topbar-main {
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 18px;
271
+ min-width: 0;
272
+ flex: 1;
273
+ }
258
274
 
259
- .workspace {
260
- flex: 1;
261
- min-height: 0;
262
- display: grid;
263
- grid-template-columns: minmax(0, 1fr) 320px;
264
- }
275
+ .app-topbar-nav {
276
+ display: flex;
277
+ align-items: center;
278
+ gap: 10px;
279
+ flex-wrap: wrap;
280
+ }
265
281
 
266
- .workspace.panel-left {
267
- grid-template-columns: 320px minmax(0, 1fr);
268
- }
282
+ .workspace {
283
+ flex: 1;
284
+ min-height: 0;
285
+ display: grid;
286
+ grid-template-columns: minmax(0, 1fr) 320px;
287
+ }
269
288
 
270
- .workspace.panel-right .canvas-host {
271
- grid-column: 1;
272
- }
289
+ .workspace.panel-left {
290
+ grid-template-columns: 320px minmax(0, 1fr);
291
+ }
273
292
 
274
- .workspace.panel-right .panel-sidebar {
275
- grid-column: 2;
276
- }
293
+ .workspace.panel-right .canvas-host {
294
+ grid-column: 1;
295
+ }
277
296
 
278
- .workspace.panel-left .panel-sidebar {
279
- grid-column: 1;
280
- }
297
+ .workspace.panel-right .panel-sidebar {
298
+ grid-column: 2;
299
+ }
281
300
 
282
- .workspace.panel-left .canvas-host {
283
- grid-column: 2;
284
- }
301
+ .workspace.panel-left .panel-sidebar {
302
+ grid-column: 1;
303
+ }
285
304
 
286
- .panel-sidebar {
287
- min-height: 0;
288
- display: flex;
289
- flex-direction: column;
290
- background: #fff;
291
- }
305
+ .workspace.panel-left .canvas-host {
306
+ grid-column: 2;
307
+ }
292
308
 
293
- .panel-host {
294
- flex: 1;
295
- min-height: 0;
296
- overflow: auto;
297
- }
309
+ .panel-sidebar {
310
+ min-height: 0;
311
+ display: flex;
312
+ flex-direction: column;
313
+ background: #ffffff;
314
+ }
298
315
 
299
- .canvas-host {
300
- position: relative;
301
- min-height: 480px;
302
- padding: 24px;
303
- display: grid;
304
- place-items: center;
305
- }
316
+ .panel-host {
317
+ flex: 1;
318
+ min-height: 0;
319
+ overflow: auto;
320
+ }
306
321
 
307
- button {
308
- border: 0;
309
- border-radius: 10px;
310
- padding: 10px 14px;
311
- cursor: pointer;
312
- background: #111827;
313
- color: #fff;
314
- font: inherit;
315
- }
322
+ .canvas-host {
323
+ position: relative;
324
+ min-height: 480px;
325
+ display: grid;
326
+ place-items: center;
327
+ }
316
328
 
317
- button.secondary {
318
- background: #e5e7eb;
319
- color: #111827;
320
- }
329
+ .brand-logo {
330
+ height: 32px;
331
+ width: 92px;
332
+ display: block;
333
+ cursor: pointer;
334
+ flex: 0 0 auto;
335
+ }
321
336
 
322
- button:focus-visible {
323
- outline: 2px solid #4338ca;
324
- outline-offset: 2px;
325
- }
337
+ .app-topbar-auth {
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: flex-end;
341
+ gap: 12px;
342
+ flex: 0 0 auto;
343
+ }
326
344
 
327
- .auth-area {
328
- position: relative;
329
- display: flex;
330
- align-items: center;
331
- }
345
+ .topbar-credits-badge {
346
+ display: inline-flex;
347
+ align-items: center;
348
+ gap: 4px;
349
+ padding: 0;
350
+ color: #2d3541;
351
+ font-size: 14px;
352
+ font-weight: 500;
353
+ line-height: 1;
354
+ white-space: nowrap;
355
+ cursor: default;
356
+ }
332
357
 
333
- .avatar-button {
334
- display: none;
335
- align-items: center;
336
- gap: 8px;
337
- background: transparent;
338
- color: #111827;
339
- padding: 0;
340
- }
358
+ .topbar-credits-value {
359
+ font-variant-numeric: tabular-nums;
360
+ }
341
361
 
342
- .avatar-image {
343
- width: 32px;
344
- height: 32px;
345
- border-radius: 999px;
346
- object-fit: cover;
347
- background: #e5e7eb;
348
- }
362
+ .credits-token-icon {
363
+ width: 20px;
364
+ height: 20px;
365
+ flex: 0 0 16px;
366
+ user-select: none;
367
+ -webkit-user-drag: none;
368
+ }
349
369
 
350
- .avatar-menu {
351
- position: absolute;
352
- right: 0;
353
- top: calc(100% + 8px);
354
- min-width: 140px;
355
- padding: 8px;
356
- display: none;
357
- background: #fff;
358
- border: 1px solid #e5e7eb;
359
- border-radius: 12px;
360
- box-shadow: 0 12px 32px rgba(15, 23, 42, 0.08);
361
- }
370
+ .credits-token-icon--sm {
371
+ width: 16px;
372
+ height: 16px;
373
+ flex-basis: 16px;
374
+ }
362
375
 
363
- .avatar-menu.is-open,
364
- .fab-menu.is-open {
365
- display: block;
366
- }
376
+ .auth-avatar-trigger {
377
+ display: inline-flex;
378
+ align-items: center;
379
+ gap: 6px;
380
+ padding: 4px 8px 4px 4px;
381
+ border-radius: 999px;
382
+ cursor: pointer;
383
+ transition: background 150ms ease;
384
+ }
367
385
 
368
- .avatar-menu button,
369
- .fab-menu button {
370
- width: 100%;
371
- }
386
+ .auth-avatar-trigger:hover {
387
+ background: var(--bg-soft);
388
+ }
372
389
 
373
- .panel-actions {
374
- padding: 12px;
375
- border-top: 1px solid #e5e7eb;
376
- }
390
+ .auth-avatar {
391
+ width: 40px;
392
+ height: 40px;
393
+ border-radius: 50%;
394
+ display: grid;
395
+ place-items: center;
396
+ overflow: hidden;
397
+ flex: 0 0 auto;
398
+ background: #f3f4f6;
399
+ color: var(--text-secondary);
400
+ font-size: 14px;
401
+ font-weight: 600;
402
+ }
377
403
 
378
- .workspace.panel-right .panel-sidebar {
379
- border-left: 1px solid #e5e7eb;
380
- }
404
+ .auth-avatar--lg {
405
+ width: 40px;
406
+ height: 40px;
407
+ font-size: 14px;
408
+ }
381
409
 
382
- .workspace.panel-left .panel-sidebar {
383
- border-right: 1px solid #e5e7eb;
384
- }
410
+ .auth-avatar img {
411
+ width: 100%;
412
+ height: 100%;
413
+ object-fit: cover;
414
+ display: block;
415
+ }
385
416
 
386
- .fab {
387
- display: flex;
388
- flex-direction: column;
389
- align-items: stretch;
390
- gap: 8px;
391
- width: 100%;
392
- }
417
+ .auth-popover-body {
418
+ min-width: 200px;
419
+ padding: 4px 0;
420
+ }
393
421
 
394
- .fab-menu {
395
- width: 100%;
396
- display: none;
397
- padding: 8px;
398
- background: #fff;
399
- border: 1px solid #e5e7eb;
400
- border-radius: 12px;
401
- box-shadow: 0 16px 40px rgba(15, 23, 42, 0.12);
402
- }
422
+ .auth-popover-header {
423
+ display: flex;
424
+ align-items: center;
425
+ gap: 12px;
426
+ padding: 12px 16px;
427
+ }
403
428
 
404
- .fab-menu button + button {
405
- margin-top: 8px;
406
- }
407
- </style>
429
+ .auth-popover-user-text {
430
+ min-width: 0;
431
+ flex: 1;
432
+ }
433
+
434
+ .auth-popover-name {
435
+ display: block;
436
+ font-size: 14px;
437
+ font-weight: 600;
438
+ color: var(--text-primary);
439
+ overflow: hidden;
440
+ text-overflow: ellipsis;
441
+ white-space: nowrap;
442
+ }
443
+
444
+ .auth-popover-sub {
445
+ display: block;
446
+ margin-top: 2px;
447
+ font-size: 12px;
448
+ color: var(--text-secondary);
449
+ overflow: hidden;
450
+ text-overflow: ellipsis;
451
+ white-space: nowrap;
452
+ }
453
+
454
+ .auth-popover-divider {
455
+ height: 1px;
456
+ background: var(--border-default);
457
+ margin: 4px 0;
458
+ }
459
+
460
+ .auth-popover-action {
461
+ display: flex;
462
+ align-items: center;
463
+ gap: 8px;
464
+ padding: 8px 16px;
465
+ font-size: 13px;
466
+ color: var(--text-secondary);
467
+ cursor: pointer;
468
+ transition: all 150ms ease;
469
+ }
470
+
471
+ .auth-popover-action:hover {
472
+ background: var(--bg-soft);
473
+ color: var(--text-primary);
474
+ }
475
+
476
+ .auth-hover-card {
477
+ position: relative;
478
+ display: inline-flex;
479
+ align-items: center;
480
+ }
481
+
482
+ .auth-hover-panel {
483
+ position: absolute;
484
+ top: calc(100% + 8px);
485
+ right: 0;
486
+ z-index: 40;
487
+ min-width: 200px;
488
+ background: #fff;
489
+ border: 1px solid var(--border-default);
490
+ border-radius: 12px;
491
+ box-shadow: 0 8px 28px rgba(17, 24, 39, 0.12);
492
+ opacity: 0;
493
+ transform: translateY(4px);
494
+ pointer-events: none;
495
+ transition: opacity 150ms ease, transform 150ms ease;
496
+ }
497
+
498
+ .auth-hover-card:hover .auth-hover-panel,
499
+ .auth-hover-card:focus-within .auth-hover-panel {
500
+ opacity: 1;
501
+ transform: translateY(0);
502
+ pointer-events: auto;
503
+ }
504
+
505
+ .panel-actions,
506
+ #sidebar-footer {
507
+ flex-shrink: 0;
508
+ padding: 14px 24px;
509
+ border-top: 1px solid var(--border-default);
510
+ background: #ffffff;
511
+ display: flex;
512
+ align-items: center;
513
+ }
514
+
515
+ .sidebar-footer-export {
516
+ width: 100%;
517
+ }
518
+
519
+ .sidebar-footer-export .sidebar-export-trigger-btn,
520
+ .sidebar-footer-export .sidebar-export-trigger-btn-wrap,
521
+ .sidebar-export-trigger-btn-wrap {
522
+ width: 100%;
523
+ }
524
+
525
+ .sidebar-export-dropdown-menu {
526
+ width: 268px;
527
+ }
528
+
529
+ .sidebar-export-trigger-btn img {
530
+ margin-right: 4px;
531
+ }
532
+
533
+ .export-credits-hint {
534
+ display: inline-flex;
535
+ align-items: center;
536
+ justify-content: center;
537
+ gap: 2px;
538
+ padding: 0;
539
+ font-size: 12px;
540
+ font-weight: 600;
541
+ line-height: 1;
542
+ color: #ff7c23;
543
+ margin-left: 10px;
544
+ white-space: nowrap;
545
+ }
546
+
547
+ .export-credits-hint-value {
548
+ color: white;
549
+ font-size: 13px;
550
+ font-weight: 700;
551
+ font-variant-numeric: tabular-nums;
552
+ }
553
+
554
+ .export-overlay-menu,
555
+ .fab-menu-content {
556
+ padding: 4px;
557
+ }
558
+
559
+ .export-overlay-item {
560
+ display: flex;
561
+ align-items: center;
562
+ gap: 8px;
563
+ padding: 8px 12px;
564
+ border-radius: 6px;
565
+ font-size: 14px;
566
+ color: var(--text-primary);
567
+ cursor: pointer;
568
+ transition: background 120ms ease;
569
+ user-select: none;
570
+ }
571
+
572
+ .export-overlay-item:hover {
573
+ background: var(--bg-soft);
574
+ }
575
+
576
+ .export-overlay-item-icon {
577
+ width: 16px;
578
+ height: 16px;
579
+ border-radius: 4px;
580
+ background: #cbd5e1;
581
+ display: inline-block;
582
+ flex: 0 0 auto;
583
+ }
584
+
585
+ .export-overlay-item-icon--svg {
586
+ background: linear-gradient(180deg, #94a3b8, #64748b);
587
+ }
588
+
589
+ .export-overlay-item-icon--studio {
590
+ background: linear-gradient(180deg, #cbd5e1, #94a3b8);
591
+ }
592
+
593
+ .workspace.panel-right .panel-sidebar {
594
+ border-left: 1px solid var(--border-default);
595
+ }
596
+
597
+ .workspace.panel-left .panel-sidebar {
598
+ border-right: 1px solid var(--border-default);
599
+ }
600
+ `;
601
+ const WORKBENCH_VUE_TEMPLATE = `
408
602
  <div class="shell">
409
- <header class="topbar">
410
- <div class="logo-area" data-role="logo-area"></div>
411
- <div class="topbar-actions">
412
- <button class="secondary" data-role="import-template">导入模板</button>
413
- <button class="secondary" data-role="export-template">生成模板</button>
414
- <div class="auth-area" data-role="auth-area">
415
- <button data-role="login">登录</button>
416
- <button class="avatar-button" data-role="avatar-button" aria-expanded="false">
417
- <img class="avatar-image" data-role="avatar-image" alt="User avatar" />
418
- </button>
419
- <div class="avatar-menu" data-role="avatar-menu">
420
- <button class="secondary" data-role="logout">退出登录</button>
603
+ <header class="topbar app-topbar">
604
+ <div class="logo-area app-topbar-main" data-role="logo-area">
605
+ <img
606
+ :src="state.logoSrc"
607
+ :alt="state.logoText || 'Atomm'"
608
+ class="brand-logo"
609
+ data-role="brand-logo"
610
+ draggable="false"
611
+ />
612
+ <div class="app-topbar-nav">
613
+ <xt-button
614
+ v-show="state.templateEnabled"
615
+ type="secondary"
616
+ size="small"
617
+ data-role="import-template"
618
+ @click="callbacks.onImportTemplate()"
619
+ >导入模板</xt-button>
620
+ <xt-button
621
+ v-show="state.templateEnabled"
622
+ type="secondary"
623
+ size="small"
624
+ data-role="export-template"
625
+ @click="callbacks.onExportTemplate()"
626
+ >生成模板</xt-button>
627
+ </div>
628
+ </div>
629
+ <div class="app-topbar-auth">
630
+ <div
631
+ v-if="state.isLogin"
632
+ class="topbar-credits-badge"
633
+ data-role="topbar-credits"
634
+ title="Remaining credits"
635
+ >
636
+ <img src="${CREDIT_ICON_DATA_URI}" alt="" class="credits-token-icon" draggable="false" />
637
+ <span class="topbar-credits-value" data-role="topbar-credits-value">{{ state.creditsBalance }}</span>
638
+ </div>
639
+
640
+ <div
641
+ v-if="state.isLogin && state.avatarMenuTrigger === 'hover'"
642
+ class="auth-hover-card"
643
+ >
644
+ <div class="auth-avatar-trigger" data-role="avatar-button" tabindex="0">
645
+ <div class="auth-avatar">
646
+ <img
647
+ v-if="state.avatarSrc"
648
+ :src="state.avatarSrc"
649
+ alt="Avatar"
650
+ data-role="avatar-image"
651
+ />
652
+ <span v-else data-role="avatar-image">{{ state.avatarText }}</span>
653
+ </div>
654
+ </div>
655
+ <div class="auth-hover-panel">
656
+ <div class="auth-popover-body" data-role="avatar-menu">
657
+ <div class="auth-popover-header">
658
+ <div class="auth-avatar auth-avatar--lg">
659
+ <img v-if="state.avatarSrc" :src="state.avatarSrc" alt="Avatar" />
660
+ <span v-else>{{ state.avatarText }}</span>
661
+ </div>
662
+ <div class="auth-popover-user-text">
663
+ <span class="auth-popover-name">{{ state.authDisplayName }}</span>
664
+ <span class="auth-popover-sub">{{ state.authSubline }}</span>
665
+ </div>
666
+ </div>
667
+ <div class="auth-popover-divider"></div>
668
+ <div class="auth-popover-action" data-role="logout" @click="callbacks.onLogout()">
669
+ Logout
670
+ </div>
671
+ </div>
421
672
  </div>
422
673
  </div>
674
+
675
+ <xt-dropdown-menu
676
+ v-else-if="state.isLogin"
677
+ trigger="click"
678
+ :portal-disabled="true"
679
+ placement="bottomRight"
680
+ >
681
+ <template #trigger>
682
+ <div class="auth-avatar-trigger" data-role="avatar-button">
683
+ <div class="auth-avatar">
684
+ <img
685
+ v-if="state.avatarSrc"
686
+ :src="state.avatarSrc"
687
+ alt="Avatar"
688
+ data-role="avatar-image"
689
+ />
690
+ <span v-else data-role="avatar-image">{{ state.avatarText }}</span>
691
+ </div>
692
+ </div>
693
+ </template>
694
+ <template #overlay>
695
+ <div class="auth-popover-body" data-role="avatar-menu">
696
+ <div class="auth-popover-header">
697
+ <div class="auth-avatar auth-avatar--lg">
698
+ <img v-if="state.avatarSrc" :src="state.avatarSrc" alt="Avatar" />
699
+ <span v-else>{{ state.avatarText }}</span>
700
+ </div>
701
+ <div class="auth-popover-user-text">
702
+ <span class="auth-popover-name">{{ state.authDisplayName }}</span>
703
+ <span class="auth-popover-sub">{{ state.authSubline }}</span>
704
+ </div>
705
+ </div>
706
+ <div class="auth-popover-divider"></div>
707
+ <div class="auth-popover-action" data-role="logout" @click="callbacks.onLogout()">
708
+ Logout
709
+ </div>
710
+ </div>
711
+ </template>
712
+ </xt-dropdown-menu>
713
+
714
+ <xt-button
715
+ size="small"
716
+ type="secondary"
717
+ data-role="login"
718
+ :loading="state.loginLoading"
719
+ v-if="!state.isLogin"
720
+ @click="callbacks.onLogin()"
721
+ >Login</xt-button>
423
722
  </div>
424
723
  </header>
425
- <div class="workspace" data-role="workspace">
724
+
725
+ <div
726
+ class="workspace"
727
+ :class="state.panelTarget === 'left' ? 'panel-left' : 'panel-right'"
728
+ data-role="workspace"
729
+ >
426
730
  <main class="canvas-host" data-role="canvas-host"></main>
427
731
  <aside class="panel-sidebar" data-role="panel-sidebar">
428
732
  <div class="panel-host" data-role="panel-host"></div>
429
- <div class="panel-actions" data-role="panel-actions">
430
- <div class="fab">
431
- <div class="fab-menu" data-role="fab-menu">
432
- <button class="secondary" data-role="open-in-studio">导入 Studio</button>
433
- <button class="secondary" data-role="export-svg">导出 SVG</button>
434
- </div>
435
- <button data-role="fab-trigger" aria-expanded="false">导出</button>
436
- </div>
733
+ <div id="sidebar-footer" class="sidebar-footer-export panel-actions" data-role="panel-actions">
734
+ <xt-dropdown-menu
735
+ trigger="click"
736
+ :portal-disabled="true"
737
+ placement="topLeft"
738
+ domTriggerClass="sidebar-export-trigger-btn-wrap"
739
+ :show-trigger-icon="false"
740
+ domContentClass="sidebar-export-dropdown-menu"
741
+ >
742
+ <template #trigger>
743
+ <xt-button class="sidebar-export-trigger-btn" block data-role="fab-trigger">
744
+ <template #icon>
745
+ <img
746
+ src="${EXPORT_ICON_DATA_URI}"
747
+ alt=""
748
+ width="16"
749
+ height="16"
750
+ draggable="false"
751
+ />
752
+ </template>
753
+ Export SVG
754
+ <template #append-icon>
755
+ <div v-if="state.isLogin" class="export-credits-hint">
756
+ <img src="${CREDIT_ICON_DATA_URI}" alt="" class="credits-token-icon credits-token-icon--sm" draggable="false" />
757
+ <span class="export-credits-hint-value" data-role="export-credits-value">{{ state.exportCreditsCost }}</span>
758
+ </div>
759
+ </template>
760
+ </xt-button>
761
+ </template>
762
+ <template #overlay>
763
+ <div class="export-overlay-menu fab-menu-content" data-role="fab-menu">
764
+ <div
765
+ v-show="state.exportEnabled"
766
+ class="export-overlay-item"
767
+ data-role="export-svg"
768
+ @click="callbacks.onExportSvg()"
769
+ >
770
+ <span class="export-overlay-item-icon export-overlay-item-icon--svg"></span>
771
+ Export SVG
772
+ </div>
773
+ <div
774
+ v-show="state.studioEnabled"
775
+ class="export-overlay-item"
776
+ data-role="open-in-studio"
777
+ @click="callbacks.onOpenInStudio()"
778
+ >
779
+ <span class="export-overlay-item-icon export-overlay-item-icon--studio"></span>
780
+ Open in Studio
781
+ </div>
782
+ </div>
783
+ </template>
784
+ </xt-dropdown-menu>
437
785
  </div>
438
786
  </aside>
439
787
  </div>
440
- <input data-role="template-file-input" type="file" accept="application/json" hidden />
788
+
789
+ <input
790
+ data-role="template-file-input"
791
+ type="file"
792
+ accept="application/json"
793
+ style="display:none"
794
+ @change="callbacks.onFileChange($event)"
795
+ />
441
796
  </div>
442
797
  `;
443
- function renderWorkbenchShell(root, _config) {
444
- root.innerHTML = WORKBENCH_TEMPLATE;
445
- return collectWorkbenchRefs(root);
798
+ function requireVue() {
799
+ const Vue = globalThis.Vue;
800
+ if (!Vue || typeof Vue.createApp !== "function") {
801
+ throw new Error(
802
+ '[generator-workbench] Vue 3 is required. Load it via CDN: <script src="https://unpkg.com/vue@3/dist/vue.global.js"><\/script>'
803
+ );
804
+ }
805
+ return Vue;
806
+ }
807
+ function injectStyles(root, cdnCssUrl) {
808
+ const link = document.createElement("link");
809
+ link.rel = "stylesheet";
810
+ link.href = cdnCssUrl || ATOMM_UI_CDN_CSS_URL;
811
+ root.appendChild(link);
812
+ const style = document.createElement("style");
813
+ style.textContent = WORKBENCH_SHELL_STYLES;
814
+ root.appendChild(style);
815
+ }
816
+ function stateFromConfig(vue, config) {
817
+ return vue.reactive({
818
+ logoText: config.logoText || config.title,
819
+ logoSrc: config.logoUrl || "https://storage-us.atomm.com/resource/xart/static/agent/imgs/atomm-logo.svg",
820
+ panelTarget: config.panelTarget || "right",
821
+ avatarMenuTrigger: config.avatarMenuTrigger || "hover",
822
+ templateEnabled: config.templateEnabled !== false,
823
+ exportEnabled: config.exportEnabled !== false,
824
+ studioEnabled: config.studioEnabled !== false,
825
+ isLogin: false,
826
+ avatarSrc: "",
827
+ avatarText: "U",
828
+ authDisplayName: "",
829
+ authSubline: "",
830
+ creditsBalance: 0,
831
+ exportCreditsCost: 1,
832
+ loginLoading: false
833
+ });
834
+ }
835
+ function createNoopCallbacks() {
836
+ return {
837
+ onLogin: () => {
838
+ },
839
+ onLogout: () => {
840
+ },
841
+ onImportTemplate: () => {
842
+ },
843
+ onExportTemplate: () => {
844
+ },
845
+ onExportSvg: () => {
846
+ },
847
+ onOpenInStudio: () => {
848
+ },
849
+ onFileChange: () => {
850
+ }
851
+ };
852
+ }
853
+ function registerFallbackComponents(app) {
854
+ app.component("xt-button", {
855
+ inheritAttrs: true,
856
+ template: '<button v-bind="$attrs"><slot name="icon" /><slot /><slot name="append-icon" /></button>'
857
+ });
858
+ app.component("xt-avatar", {
859
+ inheritAttrs: true,
860
+ template: '<span v-bind="$attrs"><slot /></span>'
861
+ });
862
+ app.component("xt-dropdown-menu", {
863
+ inheritAttrs: true,
864
+ template: '<div v-bind="$attrs"><slot name="trigger" /><slot name="overlay" /><slot /></div>'
865
+ });
866
+ app.component("xt-hover-card", {
867
+ inheritAttrs: true,
868
+ template: '<div v-bind="$attrs"><slot name="trigger" /><slot name="content" /></div>'
869
+ });
870
+ }
871
+ function renderWorkbenchShell(root, config) {
872
+ const vue = requireVue();
873
+ injectStyles(root, config.atommUiCssUrl);
874
+ const state = stateFromConfig(vue, config);
875
+ const callbacks = createNoopCallbacks();
876
+ const container = document.createElement("div");
877
+ container.setAttribute("data-workbench-vue-root", "");
878
+ root.appendChild(container);
879
+ const AtommUI = globalThis.AtommUI;
880
+ const app = vue.createApp({
881
+ setup() {
882
+ return { state, callbacks };
883
+ },
884
+ template: WORKBENCH_VUE_TEMPLATE
885
+ });
886
+ if (AtommUI) {
887
+ app.use(AtommUI);
888
+ } else {
889
+ registerFallbackComponents(app);
890
+ }
891
+ app.mount(container);
892
+ const refs = collectWorkbenchRefs(root);
893
+ return { state, callbacks, app, refs };
894
+ }
895
+ function unmountWorkbenchShell(context) {
896
+ context.app.unmount();
446
897
  }
447
898
  const DEFAULT_CONFIG = {
448
899
  title: "",
@@ -465,14 +916,16 @@ class GeneratorWorkbenchElement extends HTMLElement {
465
916
  __publicField(this, "_sdk", null);
466
917
  __publicField(this, "_runtime", null);
467
918
  __publicField(this, "_config", { ...DEFAULT_CONFIG });
468
- __publicField(this, "_refs", null);
469
919
  __publicField(this, "_mounted", false);
470
920
  __publicField(this, "_state", createInitialWorkbenchState());
471
921
  __publicField(this, "_cleanupAuth");
922
+ __publicField(this, "_cleanupCredits");
923
+ __publicField(this, "_cleanupBilling");
472
924
  __publicField(this, "_authController");
473
925
  __publicField(this, "_templateController");
474
926
  __publicField(this, "_exportController");
475
927
  __publicField(this, "_runtimeController");
928
+ __publicField(this, "_shellContext");
476
929
  this.attachShadow({ mode: "open" });
477
930
  }
478
931
  get sdk() {
@@ -520,11 +973,13 @@ class GeneratorWorkbenchElement extends HTMLElement {
520
973
  }
521
974
  this.render();
522
975
  this.bindControllers();
523
- this.bindShellEvents();
976
+ this.bindShellCallbacks();
524
977
  this.bindAuthState();
525
- await ((_a = this._runtimeController) == null ? void 0 : _a.mountCanvas(this.requireRefs().canvasHost));
978
+ await this.syncBillingState();
979
+ const refs = this.requireRefs();
980
+ await ((_a = this._runtimeController) == null ? void 0 : _a.mountCanvas(refs.canvasHost));
526
981
  await ((_b = this._runtimeController) == null ? void 0 : _b.mountPanel(
527
- this.requireRefs().panelHost,
982
+ refs.panelHost,
528
983
  this._state.activePanelFilter
529
984
  ));
530
985
  this._mounted = true;
@@ -534,10 +989,18 @@ class GeneratorWorkbenchElement extends HTMLElement {
534
989
  });
535
990
  }
536
991
  async unmount() {
537
- var _a, _b;
992
+ var _a, _b, _c, _d;
538
993
  (_a = this._cleanupAuth) == null ? void 0 : _a.call(this);
539
994
  this._cleanupAuth = void 0;
540
- await ((_b = this._runtimeController) == null ? void 0 : _b.unmountAll());
995
+ (_b = this._cleanupCredits) == null ? void 0 : _b.call(this);
996
+ this._cleanupCredits = void 0;
997
+ (_c = this._cleanupBilling) == null ? void 0 : _c.call(this);
998
+ this._cleanupBilling = void 0;
999
+ await ((_d = this._runtimeController) == null ? void 0 : _d.unmountAll());
1000
+ if (this._shellContext) {
1001
+ unmountWorkbenchShell(this._shellContext);
1002
+ this._shellContext = void 0;
1003
+ }
541
1004
  this._mounted = false;
542
1005
  }
543
1006
  refreshLayout() {
@@ -546,9 +1009,10 @@ class GeneratorWorkbenchElement extends HTMLElement {
546
1009
  try {
547
1010
  const result = await this.requireTemplateController().importTemplate(file);
548
1011
  this._state.activePanelFilter = result.panelFilter;
549
- if (this._runtimeController && this._refs) {
1012
+ const refs = this.requireRefs();
1013
+ if (this._runtimeController) {
550
1014
  await this._runtimeController.remountPanel(
551
- this._refs.panelHost,
1015
+ refs.panelHost,
552
1016
  result.panelFilter
553
1017
  );
554
1018
  }
@@ -583,9 +1047,13 @@ class GeneratorWorkbenchElement extends HTMLElement {
583
1047
  }
584
1048
  render() {
585
1049
  if (!this.shadowRoot) return;
586
- this._refs = renderWorkbenchShell(this.shadowRoot, this._config);
587
- applyWorkbenchConfig(this._refs, this._config);
1050
+ if (this._shellContext) {
1051
+ unmountWorkbenchShell(this._shellContext);
1052
+ this.shadowRoot.innerHTML = "";
1053
+ }
1054
+ this._shellContext = renderWorkbenchShell(this.shadowRoot, this._config);
588
1055
  this.syncAuthUI();
1056
+ this.syncBillingUI();
589
1057
  }
590
1058
  bindControllers() {
591
1059
  if (!this._sdk || !this._runtime) return;
@@ -605,47 +1073,35 @@ class GeneratorWorkbenchElement extends HTMLElement {
605
1073
  runtime: this._runtime
606
1074
  });
607
1075
  }
608
- bindShellEvents() {
609
- const refs = this.requireRefs();
610
- refs.loginBtn.addEventListener("click", () => {
1076
+ bindShellCallbacks() {
1077
+ if (!this._shellContext) return;
1078
+ const { callbacks } = this._shellContext;
1079
+ callbacks.onLogin = () => {
611
1080
  void this.handleLogin();
612
- });
613
- refs.logoutBtn.addEventListener("click", () => {
1081
+ };
1082
+ callbacks.onLogout = () => {
614
1083
  void this.handleLogout();
615
- });
616
- refs.importTemplateBtn.addEventListener("click", () => {
617
- refs.templateFileInput.click();
618
- });
619
- refs.exportTemplateBtn.addEventListener("click", () => {
1084
+ };
1085
+ callbacks.onImportTemplate = () => {
1086
+ this.requireRefs().templateFileInput.click();
1087
+ };
1088
+ callbacks.onExportTemplate = () => {
620
1089
  void this.exportTemplate();
621
- });
622
- refs.templateFileInput.addEventListener("change", () => {
1090
+ };
1091
+ callbacks.onExportSvg = () => {
1092
+ void this.exportSvg();
1093
+ };
1094
+ callbacks.onOpenInStudio = () => {
1095
+ void this.openInStudio();
1096
+ };
1097
+ callbacks.onFileChange = (event) => {
623
1098
  var _a;
624
- const file = (_a = refs.templateFileInput.files) == null ? void 0 : _a[0];
1099
+ const input = event.target;
1100
+ const file = (_a = input.files) == null ? void 0 : _a[0];
625
1101
  if (!file) return;
626
1102
  void this.importTemplate(file);
627
- refs.templateFileInput.value = "";
628
- });
629
- refs.fabTrigger.addEventListener("click", () => {
630
- this.toggleFabMenu();
631
- });
632
- refs.exportSvgBtn.addEventListener("click", () => {
633
- void this.exportSvg();
634
- });
635
- refs.openInStudioBtn.addEventListener("click", () => {
636
- void this.openInStudio();
637
- });
638
- refs.avatarButton.addEventListener("click", () => {
639
- this.toggleAvatarMenu();
640
- });
641
- if (this._config.avatarMenuTrigger === "hover") {
642
- refs.avatarButton.addEventListener("mouseenter", () => {
643
- this.setAvatarMenuOpen(true);
644
- });
645
- refs.avatarMenu.addEventListener("mouseleave", () => {
646
- this.setAvatarMenuOpen(false);
647
- });
648
- }
1103
+ input.value = "";
1104
+ };
649
1105
  }
650
1106
  bindAuthState() {
651
1107
  var _a;
@@ -654,52 +1110,111 @@ class GeneratorWorkbenchElement extends HTMLElement {
654
1110
  this._cleanupAuth = controller.subscribe((status) => {
655
1111
  this._state.authStatus = status;
656
1112
  this.syncAuthUI();
1113
+ void this.syncBillingState(status);
657
1114
  dispatchWorkbenchEvent(this, "auth-change", status);
658
1115
  });
1116
+ this.bindBillingSubscriptions();
1117
+ }
1118
+ bindBillingSubscriptions() {
1119
+ var _a, _b, _c, _d;
1120
+ (_a = this._cleanupCredits) == null ? void 0 : _a.call(this);
1121
+ this._cleanupCredits = void 0;
1122
+ (_b = this._cleanupBilling) == null ? void 0 : _b.call(this);
1123
+ this._cleanupBilling = void 0;
1124
+ const creditsModule = (_c = this._sdk) == null ? void 0 : _c.credits;
1125
+ if (typeof (creditsModule == null ? void 0 : creditsModule.onChange) === "function") {
1126
+ this._cleanupCredits = creditsModule.onChange((balance) => {
1127
+ this._state.creditsBalance = Number(balance) || 0;
1128
+ this.syncBillingUI();
1129
+ });
1130
+ }
1131
+ const billingModule = (_d = this._sdk) == null ? void 0 : _d.billing;
1132
+ if (typeof (billingModule == null ? void 0 : billingModule.onChange) === "function") {
1133
+ this._cleanupBilling = billingModule.onChange((usage) => {
1134
+ this.applyBillingUsage(usage);
1135
+ });
1136
+ }
659
1137
  }
660
1138
  syncAuthUI() {
661
- if (!this._refs) return;
662
- const { loginBtn, avatarButton, avatarImage } = this._refs;
1139
+ var _a, _b, _c, _d;
1140
+ if (!this._shellContext) return;
1141
+ const { state } = this._shellContext;
663
1142
  const { isLogin, userInfo } = this._state.authStatus;
664
- loginBtn.style.display = isLogin ? "none" : "";
665
- avatarButton.style.display = isLogin ? "inline-flex" : "none";
666
- avatarImage.src = isLogin ? (userInfo == null ? void 0 : userInfo.headpic) || "" : "";
667
- if (!isLogin) {
668
- this.setAvatarMenuOpen(false);
669
- }
670
- }
671
- toggleFabMenu() {
672
- this._state.menu.fabOpen = !this._state.menu.fabOpen;
673
- this.syncMenuUI();
674
- }
675
- toggleAvatarMenu() {
676
- this.setAvatarMenuOpen(!this._state.menu.avatarOpen);
677
- }
678
- setAvatarMenuOpen(open) {
679
- this._state.menu.avatarOpen = open;
680
- this.syncMenuUI();
681
- }
682
- syncMenuUI() {
683
- if (!this._refs) return;
684
- this._refs.avatarMenu.classList.toggle("is-open", this._state.menu.avatarOpen);
685
- this._refs.fabMenu.classList.toggle("is-open", this._state.menu.fabOpen);
686
- this._refs.avatarButton.setAttribute(
687
- "aria-expanded",
688
- String(this._state.menu.avatarOpen)
689
- );
690
- this._refs.fabTrigger.setAttribute(
691
- "aria-expanded",
692
- String(this._state.menu.fabOpen)
693
- );
1143
+ const displayName = ((_a = userInfo == null ? void 0 : userInfo.userName) == null ? void 0 : _a.trim()) || ((_b = userInfo == null ? void 0 : userInfo.email) == null ? void 0 : _b.trim()) || ((_c = userInfo == null ? void 0 : userInfo.phoneNumber) == null ? void 0 : _c.trim()) || "";
1144
+ const avatarText = displayName ? displayName.charAt(0).toUpperCase() : "U";
1145
+ const authSubline = ((_d = userInfo == null ? void 0 : userInfo.email) == null ? void 0 : _d.trim()) || ((userInfo == null ? void 0 : userInfo.phoneNumber) ? `${userInfo.phoneZone || ""}${userInfo.phoneNumber}` : "Connected to Generator SDK");
1146
+ state.isLogin = isLogin;
1147
+ state.avatarSrc = isLogin ? (userInfo == null ? void 0 : userInfo.headpic) || "" : "";
1148
+ state.avatarText = avatarText;
1149
+ state.authDisplayName = displayName;
1150
+ state.authSubline = isLogin ? authSubline : "";
1151
+ }
1152
+ syncBillingUI() {
1153
+ if (!this._shellContext) return;
1154
+ const { state } = this._shellContext;
1155
+ const usage = this._state.billingUsage;
1156
+ state.creditsBalance = this._state.creditsBalance;
1157
+ state.exportCreditsCost = (usage == null ? void 0 : usage.creditsPerUse) ?? (usage && "unitPrice" in usage ? Number(usage.unitPrice) || 1 : 1);
1158
+ }
1159
+ applyBillingUsage(usage) {
1160
+ this._state.billingUsage = usage || null;
1161
+ if (usage && typeof usage.creditsBalance === "number") {
1162
+ this._state.creditsBalance = usage.creditsBalance;
1163
+ }
1164
+ this.syncBillingUI();
1165
+ }
1166
+ resetBillingState() {
1167
+ this._state.creditsBalance = 0;
1168
+ this._state.billingUsage = null;
1169
+ this.syncBillingUI();
1170
+ }
1171
+ async syncBillingState(status = this._state.authStatus) {
1172
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1173
+ if (!status.isLogin || !this._sdk) {
1174
+ this.resetBillingState();
1175
+ return;
1176
+ }
1177
+ const cachedBalance = (_b = (_a = this._sdk.credits) == null ? void 0 : _a.getCachedBalance) == null ? void 0 : _b.call(_a);
1178
+ if (typeof cachedBalance === "number") {
1179
+ this._state.creditsBalance = cachedBalance;
1180
+ }
1181
+ const cachedUsage = (_d = (_c = this._sdk.billing) == null ? void 0 : _c.getCachedUsage) == null ? void 0 : _d.call(_c);
1182
+ if (cachedUsage) {
1183
+ this.applyBillingUsage(cachedUsage);
1184
+ } else {
1185
+ this.syncBillingUI();
1186
+ }
1187
+ try {
1188
+ const [creditsResult, usage] = await Promise.all([
1189
+ (_f = (_e = this._sdk.credits) == null ? void 0 : _e.getBalance) == null ? void 0 : _f.call(_e),
1190
+ (_h = (_g = this._sdk.billing) == null ? void 0 : _g.getUsage) == null ? void 0 : _h.call(_g)
1191
+ ]);
1192
+ if (typeof (creditsResult == null ? void 0 : creditsResult.quota) === "number") {
1193
+ this._state.creditsBalance = creditsResult.quota;
1194
+ }
1195
+ if (usage) {
1196
+ this.applyBillingUsage(usage);
1197
+ return;
1198
+ }
1199
+ this.syncBillingUI();
1200
+ } catch (error) {
1201
+ this.handleError("auth", error);
1202
+ }
694
1203
  }
695
1204
  async handleLogin() {
696
1205
  try {
697
1206
  this._state.busy.login = true;
1207
+ if (this._shellContext) {
1208
+ this._shellContext.state.loginLoading = true;
1209
+ }
698
1210
  await this.requireAuthController().login();
699
1211
  } catch (error) {
700
1212
  this.handleError("auth", error);
701
1213
  } finally {
702
1214
  this._state.busy.login = false;
1215
+ if (this._shellContext) {
1216
+ this._shellContext.state.loginLoading = false;
1217
+ }
703
1218
  }
704
1219
  }
705
1220
  async handleLogout() {
@@ -719,10 +1234,10 @@ class GeneratorWorkbenchElement extends HTMLElement {
719
1234
  });
720
1235
  }
721
1236
  requireRefs() {
722
- if (!this._refs) {
723
- throw new Error("[generator-workbench] refs are not ready");
1237
+ if (!this._shellContext) {
1238
+ throw new Error("[generator-workbench] shell context is not ready");
724
1239
  }
725
- return this._refs;
1240
+ return this._shellContext.refs;
726
1241
  }
727
1242
  requireAuthController() {
728
1243
  if (!this._authController) {