@philomelcc/dsh-workbench 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/client.js +100 -1
  2. package/package.json +1 -1
package/client.js CHANGED
@@ -434,7 +434,96 @@ window.__ModuleLoader__.load({
434
434
  h('path', { d: 'M4 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-4 3.5V6z', stroke: 'currentColor', strokeWidth: 1.6, strokeLinejoin: 'round' }));
435
435
  }
436
436
 
437
+ // ---------- theme switcher ----------
438
+ // `paper` builds on the light palette and overrides the alias layer only.
439
+ const PAPER_THEME = {
440
+ id: 'paper',
441
+ colorScheme: 'light',
442
+ tokens: {
443
+ '--dsw-alias-bg-base': '#f4efe3',
444
+ '--dsw-alias-bg-layer-1': '#efe9db',
445
+ '--dsw-alias-bg-layer-2': '#e7dfcd',
446
+ '--dsw-alias-bg-overlay': '#fbf7ee',
447
+ '--dsw-alias-border-l1': '#d8cfba',
448
+ '--dsw-alias-border-l2': '#c6bba2',
449
+ '--dsw-alias-brand-primary': '#8a6a45',
450
+ '--dsw-alias-label-primary': '#3b342a',
451
+ '--dsw-alias-label-secondary': '#6f6557',
452
+ '--dsw-alias-state-error-primary': '#a34a3c',
453
+ '--dsw-alias-state-warn-primary': '#a8792c',
454
+ '--dsw-alias-state-success-primary': '#4f7a5a',
455
+ '--dsw-alias-state-idle-primary': '#a49a89',
456
+ '--dsw-specific-sidebar-fill': '#efe9db',
457
+ },
458
+ };
459
+
460
+ // Swatches double as both the current-state indicator and the toggle.
461
+ const THEME_SWATCHES = [
462
+ { id: 'dark', label: '深色', color: '#15171a' },
463
+ { id: 'light', label: '浅色', color: '#f7f8fa' },
464
+ { id: 'paper', label: '纸质', color: '#f4efe3' },
465
+ ];
466
+
467
+ function createThemeSwitcher(ctx) {
468
+ function ThemeSwitcher() {
469
+ const theme = typeof ctx.get === 'function' ? ctx.get('theme') : ctx.theme;
470
+ const read = () => {
471
+ try {
472
+ const snapshot = theme && typeof theme.getTheme === 'function' ? theme.getTheme() : null;
473
+ return snapshot ? snapshot.preference : null;
474
+ } catch (_error) {
475
+ return null;
476
+ }
477
+ };
478
+ const [preference, setPreference] = React.useState(read);
479
+
480
+ React.useEffect(() => {
481
+ if (!theme || typeof theme.getTheme !== 'function' || typeof ctx.on !== 'function') return undefined;
482
+ let off = null;
483
+ try {
484
+ off = ctx.on('theme/change', () => setPreference(read()));
485
+ } catch (_error) {
486
+ off = null;
487
+ }
488
+ return () => { if (typeof off === 'function') off(); };
489
+ }, []);
490
+
491
+ return h('div', {
492
+ style: { display: 'inline-flex', alignItems: 'center', gap: 7, flex: '0 0 auto' },
493
+ title: '主题',
494
+ },
495
+ THEME_SWATCHES.map((swatch) => {
496
+ const active = preference === swatch.id;
497
+ return h('button', {
498
+ key: swatch.id,
499
+ type: 'button',
500
+ title: swatch.label,
501
+ 'aria-label': swatch.label,
502
+ 'aria-pressed': active ? 'true' : 'false',
503
+ onClick: () => {
504
+ if (!theme || typeof theme.setTheme !== 'function') return;
505
+ try { theme.setTheme(swatch.id); } catch (error) { console.warn('[workbench] setTheme failed', error); }
506
+ },
507
+ style: {
508
+ width: 15, height: 15, padding: 0, borderRadius: '50%', flex: '0 0 auto',
509
+ background: swatch.color,
510
+ border: '1px solid ' + (active ? 'var(--dsw-alias-brand-primary)' : 'var(--dsw-alias-border-l2)'),
511
+ boxShadow: active
512
+ ? '0 0 0 2px var(--dsw-alias-bg-base), 0 0 0 3px var(--dsw-alias-brand-primary)'
513
+ : 'none',
514
+ cursor: 'pointer',
515
+ transition: 'box-shadow .12s, border-color .12s',
516
+ },
517
+ });
518
+ }),
519
+ );
520
+ }
521
+ return ThemeSwitcher;
522
+ }
523
+
437
524
  function createTopBar(ctx, balance) {
525
+ const ThemeSwitcher = createThemeSwitcher(ctx);
526
+
438
527
  function BalanceChip() {
439
528
  const snap = React.useSyncExternalStore(balance.subscribe, balance.getSnapshot);
440
529
  const [hover, setHover] = React.useState(false);
@@ -637,7 +726,7 @@ window.__ModuleLoader__.load({
637
726
  iconBtn('plugins', '插件', IconPlugins),
638
727
  iconBtn('schedules', '自动化任务', IconClock),
639
728
  ),
640
- null,
729
+ h('div', { style: { flex: '0 0 auto', marginRight: 150 } }, h(ThemeSwitcher)),
641
730
  ),
642
731
  h('div', { style: { height: 45, display: 'flex', alignItems: 'center', gap: 8, padding: '0 14px', borderTop: '1px solid var(--dsw-alias-border-l1)' } },
643
732
  h('div', { ref: tabsRef, style: { position: 'relative', display: 'flex', alignItems: 'center', gap: 4, flex: 1, overflowX: 'auto', overflowY: 'hidden' } },
@@ -1240,6 +1329,16 @@ div[data-row-key^="session:"]:has(svg[data-state="ongoing"]) svg[data-state="ong
1240
1329
  layoutService = ctx.get('layout') || null;
1241
1330
  layoutGetter = () => ctx.get('layout') || null;
1242
1331
 
1332
+ // Extra palette on top of the built-in light/dark pair.
1333
+ try {
1334
+ const themeService = typeof ctx.get === 'function' ? ctx.get('theme') : null;
1335
+ if (themeService && typeof themeService.register === 'function') {
1336
+ ctx.effect(() => themeService.register(PAPER_THEME), 'workbench: paper theme');
1337
+ }
1338
+ } catch (error) {
1339
+ console.warn('[workbench] paper theme registration failed', error);
1340
+ }
1341
+
1243
1342
  // The shell's own overview lives above the tab row: it is reachable from
1244
1343
  // the top-bar "工作台" icon, so it registers a page without a tab.
1245
1344
  ctx.effect(() => api.registerView({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@philomelcc/dsh-workbench",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Workbench shell for DeepSeek Harness: a view/page module API, shared UI primitives and a top-bar switcher.",
6
6
  "license": "MIT",