dsh-pictor 0.2.1 → 0.2.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.
package/README.md CHANGED
@@ -22,7 +22,7 @@ Replace `<profile>` with the target profile name (e.g. `desktop`, `web`) and sta
22
22
 
23
23
  ## Supported dsh version
24
24
 
25
- Pictor is built and tested against **DSH Desktop 0.7.2** (bundled Harness **0.1.2-alpha.1**). The DeepSeek Harness client API moves quickly — the workspace/session services it exposes keep changing — so this plugin intentionally tracks one harness generation. Other versions (older or newer) may work or may break without notice, and are outside our support scope. If something stops working after a DSH update, update dsh to the version above rather than debugging the mismatch.
25
+ Pictor is built and tested against **DSH Desktop 0.9.0** (bundled Harness **0.1.5-rc.2**). The DeepSeek Harness client API moves quickly — the workspace/session services it exposes keep changing — so this plugin intentionally tracks one harness generation. Other versions (older or newer) may work or may break without notice, and are outside our support scope. If something stops working after a DSH update, update dsh to the version above rather than debugging the mismatch.
26
26
 
27
27
  ## Usage
28
28
 
package/lib/client.js CHANGED
@@ -53,11 +53,11 @@ const STYLE = `
53
53
  background: var(--dsw-alias-bg-layer-1, #fbfbfa); display: flex; flex-direction: column; overflow-y: auto; }
54
54
  .pt-nav-head { padding: 18px 16px 12px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
55
55
  .pt-nav-titles { flex: 1; min-width: 0; }
56
- .pt-nav-title { font-size: 18px; font-weight: 700; letter-spacing: -0.01em; margin: 0; }
56
+ .pt-nav-title { font-size: 15px; font-weight: 650; letter-spacing: -0.15px; margin: 0; }
57
57
  .pt-nav-sub { font-size: 13px; color: var(--dsw-alias-label-caption, #8a8a8a); margin: 2px 0 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
58
- .pt-nav-new { border: 0; background: var(--dsw-alias-state-business-primary, #4f7cff); color: #fff;
59
- border-radius: 10px; padding: 7px 12px; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap; flex: none; }
60
- .pt-nav-new:hover { filter: brightness(.95); }
58
+ .pt-root .pt-nav-new { border: 1px solid transparent; background: var(--dsw-alias-button-primary-fill, #4f7cff); color: var(--dsw-alias-label-primary-foreground, #fff);
59
+ border-radius: 8px; padding: 5px 12px; font-size: 13.5px; font-weight: 550; cursor: pointer; white-space: nowrap; flex: none; box-shadow: 0 1px 2px rgba(0,0,0,0.12); transition: background 140ms ease; }
60
+ .pt-root .pt-nav-new:hover { background: var(--dsw-alias-button-primary-hover, #3a6ae0); }
61
61
  .pt-nav-list { flex: 1; overflow-y: auto; padding: 4px 8px 12px; min-height: 0; }
62
62
  .pt-nav-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
63
63
  padding: 9px 10px; border-radius: 10px; border: 0; background: transparent; cursor: pointer;
@@ -78,13 +78,6 @@ const STYLE = `
78
78
  .pt-nav-settings:hover { border-color: var(--dsw-alias-border-l1, #b0b0b0); filter: brightness(.98); }
79
79
  .pt-nav-settings .glyph { color: var(--dsw-alias-state-business-primary, #4f7cff); font-size: 16px; }
80
80
  .pt-nav-settings:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.05)); }
81
- .pt-lang { display: flex; align-items: center; gap: 8px; padding: 8px 10px 0; font-size: 13px;
82
- color: var(--dsw-alias-label-caption, #888); }
83
- .pt-lang-opts { display: inline-flex; border: 1px solid var(--dsw-alias-border-l2, #d8d8d8);
84
- border-radius: 999px; overflow: hidden; }
85
- .pt-lang-opt { border: 0; background: transparent; padding: 2px 10px; font-size: 12.5px; cursor: pointer;
86
- color: var(--dsw-alias-label-secondary, #777); }
87
- .pt-lang-opt.on { background: var(--dsw-alias-state-business-primary, #4f7cff); color: #fff; font-weight: 600; }
88
81
 
89
82
  /* ---- 主区 ---- */
90
83
  .pt-main { flex: 1; min-width: 0; overflow-y: auto; padding: 22px 28px 40px; }
@@ -92,12 +85,16 @@ const STYLE = `
92
85
  .pt-title { font-size: 28px; font-weight: 700; letter-spacing: -0.01em; margin: 0 0 4px; }
93
86
  .pt-subtitle { margin: 0 0 24px; font-size: 15px; color: var(--dsw-alias-label-secondary, #666); }
94
87
  .pt-btn { border: 1px solid var(--dsw-alias-border-l2, #d8d8d8); background: var(--dsw-alias-bg-layer-2, #fff);
95
- color: var(--dsw-alias-label-primary, #1a1a1a); border-radius: 12px; padding: 9px 18px; font-size: 15px;
96
- font-weight: 550; cursor: pointer; transition: filter 150ms, background 150ms; white-space: nowrap; }
97
- .pt-btn:hover { filter: brightness(0.96); }
98
- .pt-btn:disabled { opacity: 0.45; cursor: not-allowed; }
99
- .pt-btn-primary { background: var(--dsw-alias-accent, #4f7cff); border-color: var(--dsw-alias-accent, #4f7cff); color: #fff; }
100
- .pt-btn-ghost { background: transparent; border-color: transparent; color: var(--dsw-alias-label-secondary, #666); }
88
+ color: var(--dsw-alias-label-primary, #1a1a1a); border-radius: 8px; padding: 7px 14px; font-size: 14px;
89
+ font-weight: 500; cursor: pointer; transition: background 140ms ease, border-color 140ms ease, color 140ms ease, box-shadow 140ms ease; user-select: none; white-space: nowrap; }
90
+ .pt-btn:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.05))); border-color: var(--dsw-alias-border-l3, #ccc); }
91
+ .pt-btn:active { transform: translateY(0.5px); }
92
+ .pt-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4f7cff); outline-offset: 2px; }
93
+ .pt-btn:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; }
94
+ .pt-btn-primary { background: var(--dsw-alias-button-primary-fill, #4f7cff); border-color: transparent; color: var(--dsw-alias-label-primary-foreground, #fff); font-weight: 550; box-shadow: 0 1px 2px rgba(0,0,0,0.12); }
95
+ .pt-btn-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover, #3a6ae0); }
96
+ .pt-btn-ghost { background: transparent; border-color: transparent; color: var(--dsw-alias-label-dimmed, #777); }
97
+ .pt-btn-ghost:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.05)); color: var(--dsw-alias-label-primary, #1a1a1a); border-color: transparent; }
101
98
  .pt-card { background: var(--dsw-alias-bg-layer-1, #fff); border: 1px solid var(--dsw-alias-border-l2, #e2e2e2);
102
99
  border-radius: 16px; padding: 18px 20px; }
103
100
  .pt-card-row { display: flex; align-items: center; gap: 14px; justify-content: space-between; }
@@ -120,10 +117,12 @@ const STYLE = `
120
117
  .pt-badge-gated { color: #2563eb; }
121
118
  .pt-badge-done { color: #15803d; }
122
119
  .pt-badge-idle { color: var(--dsw-alias-label-secondary, #777); }
123
- .pt-input { width: 100%; border: 1px solid var(--dsw-alias-border-l2, #d8d8d8); border-radius: 12px;
120
+ .pt-input { width: 100%; border: 1px solid var(--dsw-alias-border-l2, #d8d8d8); border-radius: 8px;
124
121
  background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #1a1a1a);
125
- padding: 10px 14px; font-size: 15px; outline: none; }
126
- .pt-input:focus, .pt-rich:focus { border-color: var(--dsw-alias-accent, #4f7cff); }
122
+ padding: 8px 12px; font-size: 14px; outline: none; transition: border-color 140ms ease, box-shadow 140ms ease; }
123
+ .pt-input::placeholder { color: var(--dsw-alias-label-caption, #888); }
124
+ .pt-input:hover { border-color: var(--dsw-alias-border-l3, #c5c5c5); }
125
+ .pt-input:focus, .pt-rich:focus { border-color: var(--dsw-alias-brand-primary, #4f7cff); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary, #4f7cff) 18%, transparent); }
127
126
  .pt-textarea { min-height: 90px; resize: vertical; }
128
127
  .pt-rich { min-height: 180px; max-height: 320px; overflow-y: auto; padding: 12px 14px; }
129
128
  .pt-rich:empty::before { content: attr(data-placeholder); color: var(--dsw-alias-label-caption, #999); }
@@ -206,7 +205,7 @@ const STYLE = `
206
205
  .pt-tab.active { background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #1a1a1a);
207
206
  border-color: var(--dsw-alias-accent, #4f7cff); }
208
207
  .pt-field { margin-bottom: 14px; }
209
- .pt-field-label { display: block; font-size: 13.5px; font-weight: 550; color: var(--dsw-alias-label-secondary, #666);
208
+ .pt-field-label { display: block; font-size: 13.5px; font-weight: 500; color: var(--dsw-alias-label-primary, #1a1a1a);
210
209
  margin-bottom: 6px; }
211
210
  .pt-modal-backdrop { position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.38);
212
211
  display: flex; align-items: center; justify-content: center; padding: 24px; }
@@ -368,13 +367,22 @@ const I18N = {
368
367
  send: 'Send', langLabel: 'Language', loading: 'Loading…',
369
368
  },
370
369
  }
371
- let savedLang = 'zh'
372
- try { savedLang = typeof localStorage !== 'undefined' ? localStorage.getItem('pictor-lang') : null } catch { /* 沙箱可能没有 storage */ }
370
+ function readBandungLang() {
371
+ try {
372
+ if (typeof window !== 'undefined' && window.__dshAppDock__ && window.__dshAppDock__.lang) return window.__dshAppDock__.lang.get()
373
+ return typeof localStorage !== 'undefined' ? (localStorage.getItem('bandung-lang') === 'en' ? 'en' : 'zh') : 'zh'
374
+ } catch { return 'zh' }
375
+ }
373
376
  const langStore = {
374
- val: savedLang === 'en' ? 'en' : 'zh',
377
+ val: readBandungLang(),
375
378
  subs: new Set(),
376
379
  emit() { for (const f of this.subs) f() },
377
- set(v) { this.val = v === 'en' ? 'en' : 'zh'; try { localStorage.setItem('pictor-lang', this.val) } catch { /* ignore */ } this.emit() },
380
+ set(v) {
381
+ this.val = v === 'en' ? 'en' : 'zh'
382
+ try { localStorage.setItem('bandung-lang', this.val) } catch { /* ignore */ }
383
+ if (typeof window !== 'undefined' && window.__dshAppDock__ && window.__dshAppDock__.lang) window.__dshAppDock__.lang.set(this.val)
384
+ this.emit()
385
+ },
378
386
  subscribe(f) { this.subs.add(f); return () => { this.subs.delete(f) } },
379
387
  }
380
388
  function useLang() {
@@ -387,22 +395,17 @@ function t(key, vars) {
387
395
  if (vars) { for (const k of Object.keys(vars)) text = text.replace('{' + k + '}', String(vars[k])) }
388
396
  return text
389
397
  }
390
- function LangSwitch() {
391
- const lang = useLang()
392
- const opt = (code, label) => h('button', {
393
- className: 'pt-lang-opt' + (lang === code ? ' on' : ''),
394
- onClick: () => langStore.set(code),
395
- }, label)
396
- return h('div', { className: 'pt-lang' },
397
- h('span', { className: 'pt-lang-label' }, t('langLabel')),
398
- h('div', { className: 'pt-lang-opts' }, opt('zh', '中文'), opt('en', 'English')))
399
- }
400
398
 
401
399
  // ---------- 工具 ----------
402
400
 
401
+ // RPC 走 webServer 的 /pictor/rpc/* prefix 路由(同源 HTTP,不再经 connection.rpc.call)。
403
402
  function rpc(ctx, endpoint, payload) {
404
403
  // 服务端 schema 要求 payload 字段必须存在(对象);缺省补 {},否则序列化丢键被拒。
405
- return ctx.connection.rpc.call(RPC, endpoint, payload === undefined ? {} : payload).then((result) => {
404
+ return fetch(RPC + '/rpc/' + encodeURIComponent(endpoint), {
405
+ method: 'POST',
406
+ headers: { 'content-type': 'application/json' },
407
+ body: JSON.stringify(payload === undefined ? {} : payload),
408
+ }).then((r) => r.json()).then((result) => {
406
409
  if (result && result.ok === false) {
407
410
  const msg = result.error && result.error.message ? result.error.message : String(result.error || 'RPC 调用失败')
408
411
  throw new Error(msg)
@@ -492,14 +495,27 @@ function sanitizePaste(html) {
492
495
  // 在就调哪个,老版本前端不受影响。
493
496
  function sessionConnectFn(ctx, ws) {
494
497
  let uiWs = null
495
- try { uiWs = ctx.uiWorkspace || ((ctx.get && ctx.get('uiWorkspace')) || null) } catch { /* ignore */ }
498
+ try { uiWs = (ctx.get && ctx.get('uiWorkspace')) || null } catch { /* ignore */ }
496
499
  return (uiWs && typeof uiWs.connectWorkspace === 'function')
497
500
  ? uiWs.connectWorkspace.bind(uiWs)
498
501
  : (ws && typeof ws.connectWorkspace === 'function') ? ws.connectWorkspace.bind(ws) : null
499
502
  }
500
503
 
504
+ /**
505
+ * 从 workspaces.connectWorkspace / sessions.create 的返回里取 sessionId。
506
+ * 0.1.5 起 create/connectWorkspace 返回 {ok, value:{sessionId}} 信封;
507
+ * 早版本直接返回 id 字符串或 {id}。这里全兼容。
508
+ */
509
+ function extractSid(created) {
510
+ if (!created) return null
511
+ if (typeof created === 'string') return created
512
+ return created.sessionId || created.id
513
+ || (created.value && (created.value.sessionId || created.value.id))
514
+ || null
515
+ }
516
+
501
517
  async function pictorWorkspace(ctx) {
502
- const ws = ctx.workspaces || (ctx.get && ctx.get('workspaces'))
518
+ const ws = (ctx.get && ctx.get('workspaces'))
503
519
  const base = await rpc(ctx, 'config.get').then((c) => (c && c.dataRoot) || '')
504
520
  // 这里只用 list/create/delete/rename,新旧 services 都有;connectWorkspace
505
521
  // 的可用性由 driveProjectSession 检查。
@@ -542,13 +558,13 @@ async function pictorWorkspace(ctx) {
542
558
  }
543
559
 
544
560
  async function driveProjectSession(ctx, prompt) {
545
- const ws = ctx.workspaces || (ctx.get && ctx.get('workspaces'))
546
- const sessionsSvc = ctx.sessions || (ctx.get && ctx.get('sessions'))
561
+ const ws = (ctx.get && ctx.get('workspaces'))
562
+ const sessionsSvc = (ctx.get && ctx.get('sessions'))
547
563
  const connect = sessionConnectFn(ctx, ws)
548
564
  const canCreate = !!(sessionsSvc && typeof sessionsSvc.create === 'function')
549
565
  if (!(connect || canCreate) || !(sessionsSvc && typeof sessionsSvc.binding === 'function')) {
550
566
  let uiWs = null
551
- try { uiWs = ctx.uiWorkspace || ((ctx.get && ctx.get('uiWorkspace')) || null) } catch { /* ignore */ }
567
+ try { uiWs = (ctx.get && ctx.get('uiWorkspace')) || null } catch { /* ignore */ }
552
568
  const diag = { ws: !!ws, ses: !!sessionsSvc && typeof sessionsSvc.binding === 'function', uiws: !!uiWs, conn: !!connect, create: canCreate }
553
569
  console.warn('[pictor] session services unavailable', diag)
554
570
  try { window.__pictor_diag = diag } catch { /* ignore */ }
@@ -559,7 +575,7 @@ async function driveProjectSession(ctx, prompt) {
559
575
  let sessionId
560
576
  try {
561
577
  const created = connect ? await connect(wid) : await sessionsSvc.create({ workspaceId: wid })
562
- sessionId = created && typeof created === 'object' && created.id ? created.id : created
578
+ sessionId = extractSid(created)
563
579
  }
564
580
  catch (e) { throw new Error('创建会话失败:' + String((e && e.message) || e)) }
565
581
  const bound = sessionsSvc && typeof sessionsSvc.binding === 'function' ? sessionsSvc.binding(sessionId) : null
@@ -575,7 +591,7 @@ async function driveProjectSession(ctx, prompt) {
575
591
  async function drivePrompt(ctx, id, message) {
576
592
  const p = await rpc(ctx, 'project.prompt', { id, message })
577
593
  if (p && p.sessionId) {
578
- const sessionsSvc = ctx.sessions || (ctx.get && ctx.get('sessions'))
594
+ const sessionsSvc = (ctx.get && ctx.get('sessions'))
579
595
  if (sessionsSvc && typeof sessionsSvc.binding === 'function') {
580
596
  try {
581
597
  const bound = sessionsSvc.binding(p.sessionId)
@@ -1666,7 +1682,7 @@ function Workbench(props) {
1666
1682
  h('button', { className: 'pt-nav-settings', onClick: () => setView('settings') },
1667
1683
  h('span', { className: 'glyph' }, '⚙'),
1668
1684
  t('navSettings')),
1669
- h(LangSwitch, null)))
1685
+ ))
1670
1686
 
1671
1687
  return h('div', { className: 'pt-root pt-workbench' },
1672
1688
  nav,
@@ -1712,9 +1728,9 @@ function selectedLabel(ids) {
1712
1728
  // 调整了客户端 API),host 落盘 ~/.pictor/diag.jsonl,无需 DevTools 即可排查。
1713
1729
  function pictorDiag(ctx, phase) {
1714
1730
  let ws = null, ses = null, ui = null
1715
- try { ws = ctx && (ctx.workspaces || (ctx.get && ctx.get('workspaces'))) } catch { /* ignore */ }
1716
- try { ses = ctx && (ctx.sessions || (ctx.get && ctx.get('sessions'))) } catch { /* ignore */ }
1717
- try { ui = ctx && (ctx.uiWorkspace || (ctx.get && ctx.get('uiWorkspace'))) } catch { /* ignore */ }
1731
+ try { ws = ctx && (ctx.get && ctx.get('workspaces')) } catch { /* ignore */ }
1732
+ try { ses = ctx && (ctx.get && ctx.get('sessions')) } catch { /* ignore */ }
1733
+ try { ui = ctx && (ctx.get && ctx.get('uiWorkspace')) } catch { /* ignore */ }
1718
1734
  const body = {
1719
1735
  phase,
1720
1736
  ws: !!ws, ses: !!ses, ui: !!ui,
@@ -1732,7 +1748,7 @@ function pictorDiag(ctx, phase) {
1732
1748
 
1733
1749
  function apply(ctx) {
1734
1750
  try { pictorDiag(ctx, 'apply') } catch { /* ignore */ }
1735
- console.info('[dsh-pictor] apply 被调用', { hasSlots: Boolean(ctx && ctx.slots) })
1751
+ console.info('[dsh-pictor] apply 被调用', { hasSlots: Boolean(ctx.get && ctx.get('slots')) })
1736
1752
  if (typeof document !== 'undefined') {
1737
1753
  const styleId = 'dsh-pictor-style'
1738
1754
  if (!document.getElementById(styleId)) {
@@ -1742,7 +1758,7 @@ function apply(ctx) {
1742
1758
  document.head.appendChild(style)
1743
1759
  }
1744
1760
  }
1745
- const slots = ctx.slots || (ctx.get && ctx.get('slots'))
1761
+ const slots = (ctx.get && ctx.get('slots'))
1746
1762
  if (!slots) return
1747
1763
 
1748
1764
  const panel = { open: false, subs: new Set() }
@@ -1751,12 +1767,12 @@ function apply(ctx) {
1751
1767
  panel.close = () => { if (panel.open) { panel.open = false; panel.emit() } }
1752
1768
  panel.subscribe = (fn) => { panel.subs.add(fn); return () => { panel.subs.delete(fn) } }
1753
1769
  // 点选 dsh 左侧会话(或面板外任意处)自动收起工作台:
1754
- // 只排除面板本身与 Pictor 启动按钮;侧栏会话此刻在面板左侧始终可点。
1770
+ // 只排除面板本身;侧栏会话此刻在面板左侧始终可点(入口已交给应用坞)。
1755
1771
  if (typeof document !== 'undefined') {
1756
1772
  document.addEventListener('mousedown', (e) => {
1757
1773
  if (!panel.open) return
1758
1774
  const t = e.target
1759
- if (t && typeof t.closest === 'function' && (t.closest('.pt-shell-panel') || t.closest('.pt-footer-action'))) return
1775
+ if (t && typeof t.closest === 'function' && t.closest('.pt-shell-panel')) return
1760
1776
  panel.close()
1761
1777
  })
1762
1778
  }
@@ -1798,24 +1814,22 @@ function apply(ctx) {
1798
1814
  h(Workbench, { ctx, key: 'shell' })))
1799
1815
  }
1800
1816
 
1801
- function FooterAction() {
1802
- useLang() // footer 文案随语言刷新
1803
- const open = usePanelOpen()
1804
- return h('div', {
1805
- className: 'pt-footer-action' + (open ? ' on' : ''),
1806
- role: 'button',
1807
- tabIndex: 0,
1808
- onClick: () => panel.toggle(),
1809
- onKeyDown: (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); panel.toggle() } },
1810
- title: open ? t('close') : t('open'),
1811
- 'aria-expanded': open ? 'true' : 'false',
1812
- }, h('span', { className: 'glyph' }, '◈'), 'Pictor')
1817
+ // 入坞:dsh-app-dock 是依赖,入口交给坞(含 ready 延迟注册),自占 footer 槽移除。
1818
+ const registerWithDock = () => {
1819
+ if (typeof window === 'undefined' || !window.__dshAppDock__) return
1820
+ window.__dshAppDock__.register({ id: 'dsh-pictor', label: 'Pictor', icon: '◈', order: 20, onToggle: () => panel.toggle() })
1821
+ if (window.__dshAppDock__.lang) {
1822
+ window.__dshAppDock__.lang.subscribe(() => {
1823
+ const v = window.__dshAppDock__.lang.get()
1824
+ if (langStore.val !== v) { langStore.val = v; langStore.emit() }
1825
+ })
1826
+ }
1813
1827
  }
1828
+ if (typeof window !== 'undefined' && !window.__dshAppDock__) {
1829
+ window.addEventListener('dsh-app-dock:ready', registerWithDock, { once: true })
1830
+ }
1831
+ registerWithDock()
1814
1832
 
1815
- slots.inject('sidebar.footer.action', () => slots.register(
1816
- { name: 'sidebar.footer.action', id: 'dsh-pictor', order: 20, label: 'Pictor' },
1817
- () => h(FooterAction, null),
1818
- ))
1819
1833
  slots.inject('shell.overlay', () => slots.register(
1820
1834
  { name: 'shell.overlay', id: 'dsh-pictor', order: 10, label: 'Pictor' },
1821
1835
  () => h(WorkbenchPanel, null),
package/lib/index.js CHANGED
@@ -106,7 +106,7 @@ function initialPrompt(dir) {
106
106
  return prompts.join("\n");
107
107
  }
108
108
  function apply(ctx, config = {}) {
109
- const connection = ctx.connection;
109
+ const connection = ctx.get?.("connection");
110
110
  const base = config.dataDir || process.env.PICTOR_HOME || join(homedir(), ".pictor");
111
111
  const INDEX_JSON = join(base, "index.json");
112
112
  const CONFIG_JSON = join(base, "pictor-config.json");
@@ -171,7 +171,7 @@ function apply(ctx, config = {}) {
171
171
  }
172
172
  function credentialsService() {
173
173
  try {
174
- return ctx.credentials || ctx.get?.("credentials");
174
+ return ctx.get?.("credentials");
175
175
  } catch {
176
176
  return null;
177
177
  }
@@ -676,20 +676,36 @@ function apply(ctx, config = {}) {
676
676
  throw new Error(`dsh-pictor: \u672A\u77E5\u7AEF\u70B9 ${endpoint}`);
677
677
  }
678
678
  }
679
- if (connection && connection.rpc && connection.rpc.handle) {
680
- connection.rpc.handle("/pictor", async (endpoint, payload) => {
681
- try {
682
- return { ok: true, value: await handle(endpoint, payload || {}) };
683
- } catch (e) {
684
- logError(`rpc:${endpoint}`, e);
685
- return { ok: false, error: { code: "internal", message: String(e?.message || e), details: {} } };
686
- }
687
- }, { authority: "loopback" });
688
- }
689
- const wsA = ctx.webServer || ctx.get?.("webServer");
679
+ const wsA = ctx.get?.("webServer");
690
680
  if (wsA && typeof wsA.register === "function") {
691
681
  const IMG_CACHE = { "content-type": "image/png", "cache-control": "public, max-age=3600" };
692
682
  const WEBP_CACHE = { "content-type": "image/webp", "cache-control": "public, max-age=3600" };
683
+ const RPC_PREFIX = "/pictor/rpc";
684
+ wsA.register({ kind: "prefix", path: RPC_PREFIX, handler: async (req, res) => {
685
+ try {
686
+ const pathname = new URL(req.url || "/", "http://x").pathname;
687
+ const endpoint = decodeURIComponent(pathname.slice(RPC_PREFIX.length + 1));
688
+ let payload = {};
689
+ if (req.method === "POST" || req.method === "PUT") {
690
+ const chunks = [];
691
+ for await (const c of req) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
692
+ const raw = Buffer.concat(chunks).toString("utf8");
693
+ if (raw) payload = JSON.parse(raw);
694
+ }
695
+ try {
696
+ const value = await handle(endpoint, payload || {});
697
+ res.writeHead(200, { "content-type": "application/json" });
698
+ res.end(JSON.stringify({ ok: true, value }));
699
+ } catch (e) {
700
+ logError(`rpc:${endpoint}`, e);
701
+ res.writeHead(200, { "content-type": "application/json" });
702
+ res.end(JSON.stringify({ ok: false, error: { code: "internal", message: String(e?.message || e), details: {} } }));
703
+ }
704
+ } catch {
705
+ res.writeHead(400);
706
+ res.end("bad request");
707
+ }
708
+ } });
693
709
  wsA.register({ kind: "exact", path: "/pictor/asset/empty-state.png", handler: (_req, res) => {
694
710
  res.writeHead(200, IMG_CACHE);
695
711
  res.end(readFileSync(join(PLUGIN_ROOT, "assets", "empty-state.png")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-pictor",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Pictor:文档转信息图工作台。每项目一个连贯 dsh 会话,GUI 为 shell.overlay 工作台(左栏项目、右栏三步:提取/方案/渲染),行为全部交给 DSH 智能运行时。",
5
5
  "type": "module",
6
6
  "repository": {
@@ -30,10 +30,10 @@
30
30
  },
31
31
  "client": {
32
32
  "inject": [
33
- "@deepseek-ai/dsh-client-runtime",
34
33
  "@deepseek-ai/dsh-client-ui-theme",
35
34
  "@deepseek-ai/dsh-client-ui-conversation",
36
- "@deepseek-ai/dsh-client-ui-settings"
35
+ "@deepseek-ai/dsh-client-ui-settings",
36
+ "@deepseek-ai/dsh-client-ui-workspace"
37
37
  ],
38
38
  "platform": "web"
39
39
  }
@@ -61,11 +61,15 @@
61
61
  "prepare": "node scripts/build.mjs"
62
62
  },
63
63
  "license": "MIT",
64
+ "dependencies": {
65
+ "dsh-app-dock": "^0.1.2"
66
+ },
64
67
  "peerDependencies": {
65
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
68
+ "@deepseek-ai/cordis": "^4.0.2",
66
69
  "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
67
70
  "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
68
71
  "@deepseek-ai/dsh-client-ui-theme": "^0.1.0-rc.6",
72
+ "@deepseek-ai/dsh-client-ui-workspace": "^0.1.0-rc.6",
69
73
  "react": "^18.2.0",
70
74
  "react-dom": "^18.2.0"
71
75
  },
@@ -81,4 +85,4 @@
81
85
  "@playwright/test": "^1.62.1",
82
86
  "esbuild": "^0.28.2"
83
87
  }
84
- }
88
+ }