@ninkch/dsh-artcraft 0.2.0 → 0.2.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.
Files changed (2) hide show
  1. package/client.js +74 -66
  2. package/package.json +2 -2
package/client.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // @ninkch/dsh-artcraft — client half.
2
- // 侧栏 "ArtCraft" 按钮 + 面板:工具链状态 / 自更新(检查 + 一键应用)。
2
+ // 不占侧栏/主面板:状态与「检查更新」挂在 插件页 → 本插件详情卡 上
3
+ // (plugins.detail.badge / plugins.detail.section)。
3
4
  window.__ModuleLoader__.load({
4
5
  id: '@ninkch/dsh-artcraft',
5
6
  factory(require) {
@@ -8,28 +9,27 @@ window.__ModuleLoader__.load({
8
9
  const { useState, useEffect, useCallback, useRef } = React;
9
10
 
10
11
  const NS = 'artcraft';
11
- const PANEL_ID = 'artcraft';
12
12
  const API = '/artcraft/api';
13
13
 
14
14
  const CSS = `
15
- .dshac-panel { padding: 14px; font-size: 13px; line-height: 1.5; overflow: auto; height: 100%; box-sizing: border-box; }
16
- .dshac-h { font-size: 14px; font-weight: 600; margin: 0 0 10px; display: flex; align-items: center; gap: 8px; }
17
- .dshac-card { border: 1px solid var(--dsw-border-primary, rgba(128,128,128,.35)); border-radius: 10px; padding: 12px; margin-bottom: 12px; }
18
- .dshac-title { font-weight: 600; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; }
19
- .dshac-row { display: flex; align-items: center; gap: 8px; margin: 4px 0; }
20
- .dshac-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
15
+ .dshac-card { border: 1px solid var(--dsw-border-primary, rgba(128,128,128,.35)); border-radius: 10px; padding: 12px; font-size: 13px; line-height: 1.5; }
16
+ .dshac-title { font-weight: 600; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; gap: 8px; }
17
+ .dshac-row { display: flex; align-items: center; gap: 8px; margin: 4px 0; flex-wrap: wrap; }
18
+ .dshac-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; display: inline-block; }
21
19
  .dshac-dot.ok { background: #34c759; } .dshac-dot.bad { background: #ff5f57; } .dshac-dot.unknown { background: #8e8e93; }
22
20
  .dshac-mono { font-family: ui-monospace, Consolas, monospace; font-size: 12px; word-break: break-all; }
23
21
  .dshac-muted { opacity: .65; font-size: 12px; }
24
22
  .dshac-btn { border: 1px solid var(--dsw-border-primary, rgba(128,128,128,.4)); background: transparent; color: inherit;
25
- border-radius: 7px; padding: 4px 12px; font-size: 12px; cursor: pointer; }
23
+ border-radius: 7px; padding: 3px 10px; font-size: 12px; cursor: pointer; }
26
24
  .dshac-btn:hover:not(:disabled) { background: rgba(128,128,128,.15); }
27
25
  .dshac-btn:disabled { opacity: .5; cursor: default; }
28
26
  .dshac-btn.primary { background: rgba(80,120,255,.25); }
29
27
  .dshac-out { white-space: pre-wrap; font-family: ui-monospace, Consolas, monospace; font-size: 11px;
30
- background: rgba(128,128,128,.12); border-radius: 7px; padding: 8px; margin-top: 8px; max-height: 180px; overflow: auto; }
28
+ background: rgba(128,128,128,.12); border-radius: 7px; padding: 8px; margin-top: 8px; max-height: 160px; overflow: auto; }
31
29
  .dshac-note { margin-top: 8px; color: var(--dsw-alias-state-warning-primary, #d69e2e); font-size: 12px; }
32
30
  .dshac-ok { color: var(--dsw-alias-state-success-primary, #34c759); }
31
+ .dshac-chip { display: inline-flex; align-items: center; border: 1px solid rgba(80,120,255,.5); color: inherit;
32
+ background: rgba(80,120,255,.18); border-radius: 999px; padding: 1px 9px; font-size: 11px; }
33
33
  `;
34
34
 
35
35
  async function api(path, opts) {
@@ -43,25 +43,40 @@ window.__ModuleLoader__.load({
43
43
  } finally { if (timer) clearTimeout(timer); }
44
44
  }
45
45
 
46
+ /** 详情页 subject 兼容提取:判断当前详情页是否本插件 */
47
+ function isOurPage(props) {
48
+ const sub = props && props.subject ? props.subject : props;
49
+ if (!sub) return false;
50
+ const name = sub.packageName ?? sub.name ?? sub.moduleName ?? sub.id ?? (typeof sub === 'string' ? sub : '');
51
+ return typeof name === 'string' && name.includes('dsh-artcraft');
52
+ }
53
+
46
54
  function Dot({ ok }) {
47
55
  return h('span', { className: 'dshac-dot ' + (ok === true ? 'ok' : ok === false ? 'bad' : 'unknown') });
48
56
  }
49
57
 
50
- function PanelIcon() {
51
- return h('svg', { width: 18, height: 18, viewBox: '0 0 20 20', fill: 'none' },
52
- h('rect', { x: 3, y: 3, width: 14, height: 14, rx: 3, stroke: 'currentColor', 'stroke-width': 1.5 }),
53
- h('path', { d: 'M6.5 10h4M8.5 8v4', stroke: 'currentColor', 'stroke-width': 1.5, 'stroke-linecap': 'round' }),
54
- h('circle', { cx: 13.5, cy: 13, r: 1.6, fill: 'currentColor' }),
55
- );
58
+ /** 详情页标题旁的徽标:有新版本时出现 */
59
+ function UpdateBadge(props) {
60
+ const [latest, setLatest] = useState(null);
61
+ useEffect(() => {
62
+ if (!isOurPage(props)) return;
63
+ let ok = true;
64
+ api('/update/check').then((u) => { if (ok && u?.updateAvailable) setLatest(u.latest); }).catch(() => {});
65
+ return () => { ok = false; };
66
+ }, []);
67
+ if (!isOurPage(props) || !latest) return null;
68
+ return h('span', { className: 'dshac-chip', title: 'npm 上有新版本,到本页一键更新' }, '有新版本 v' + latest);
56
69
  }
57
70
 
58
- function ArtCraftPage() {
71
+ /** 详情页内容区:工具链状态 + 更新 */
72
+ function StatusSection(props) {
59
73
  const [status, setStatus] = useState(null);
60
74
  const [statusErr, setStatusErr] = useState(null);
61
75
  const [upd, setUpd] = useState(null);
62
76
  const [busy, setBusy] = useState('');
63
77
  const [out, setOut] = useState('');
64
78
  const [needRestart, setNeedRestart] = useState(false);
79
+ const ours = isOurPage(props);
65
80
  const alive = useRef(true);
66
81
  useEffect(() => () => { alive.current = false; }, []);
67
82
 
@@ -70,10 +85,7 @@ window.__ModuleLoader__.load({
70
85
  const s = await api('/status');
71
86
  if (!alive.current) return;
72
87
  setStatus(s); setStatusErr(null);
73
- } catch (e) {
74
- if (!alive.current) return;
75
- setStatusErr(String(e.message || e));
76
- }
88
+ } catch (e) { if (alive.current) setStatusErr(String(e.message || e)); }
77
89
  }, []);
78
90
  const checkUpdate = useCallback(async () => {
79
91
  setBusy('check'); setOut('');
@@ -98,7 +110,12 @@ window.__ModuleLoader__.load({
98
110
  finally { if (alive.current) setBusy(''); }
99
111
  }, []);
100
112
 
101
- useEffect(() => { loadStatus(); checkUpdate(); }, [loadStatus, checkUpdate]);
113
+ useEffect(() => {
114
+ if (!ours) return;
115
+ loadStatus(); checkUpdate();
116
+ }, [ours, loadStatus, checkUpdate]);
117
+
118
+ if (!ours) return null;
102
119
 
103
120
  const bins = status?.bins ?? {};
104
121
  const binRow = (key, label) => {
@@ -110,52 +127,39 @@ window.__ModuleLoader__.load({
110
127
  );
111
128
  };
112
129
 
113
- return h('div', { className: 'dshac-panel' },
114
- h('div', { className: 'dshac-h' }, 'ArtCraft Bridge',
115
- status ? h('span', { className: 'dshac-muted' }, 'v' + status.version) : null),
116
-
130
+ return h('div', { className: 'dshac-card' },
131
+ h('div', { className: 'dshac-title' }, 'ArtCraft 工具链与更新',
132
+ h('button', { className: 'dshac-btn', onClick: loadStatus, disabled: busy !== '' }, '刷新')),
117
133
  statusErr
118
- ? h('div', { className: 'dshac-card' },
119
- h('div', { className: 'dshac-title' }, '未连接'),
120
- h('div', { className: 'dshac-muted' }, statusErr + ' —— 插件可能未注册或 Harness 需要重启。'))
121
- : h('div', { className: 'dshac-card' },
122
- h('div', { className: 'dshac-title' }, '工具链',
123
- h('button', { className: 'dshac-btn', onClick: loadStatus, disabled: busy !== '' }, '刷新')),
134
+ ? h('div', { className: 'dshac-muted' }, '未连接:' + statusErr + '(插件可能未注册或 Harness 需要重启)')
135
+ : h('div', null,
124
136
  binRow('photocraft', 'PhotoCraft (CLI)'),
125
137
  binRow('filmcraft', 'FilmCraft (CLI)'),
126
138
  binRow('effectcraft', 'EffectCraft (CLI)'),
127
139
  status?.dataRoot ? h('div', { className: 'dshac-row dshac-muted' },
128
140
  '数据目录:', h('span', { className: 'dshac-mono' }, status.dataRoot.path)) : null),
129
-
130
- h('div', { className: 'dshac-card' },
131
- h('div', { className: 'dshac-title' }, '插件更新',
132
- h('button', { className: 'dshac-btn primary', onClick: checkUpdate, disabled: busy !== '' },
133
- busy === 'check' ? '检查中…' : '检查更新')),
134
- upd
135
- ? h('div', { className: 'dshac-row' },
136
- h('span', null, '当前 v' + (upd.current ?? '?')),
137
- h('span', { className: 'dshac-muted' }, '→'),
138
- h('span', { className: upd.updateAvailable ? 'dshac-ok' : 'dshac-muted' },
139
- 'npm latest v' + (upd.latest ?? '—')),
140
- upd.mode ? h('span', { className: 'dshac-muted' },
141
- '(' + (upd.mode === 'git' ? 'git 仓库 · ' + (upd.branch || '?') + (upd.gitClean === false ? ' · 有本地改动' : ' · 工作区干净') : 'npm 安装') + ')') : null)
142
- : h('div', { className: 'dshac-muted' }, '尚未检查'),
143
- h('div', { style: { marginTop: '8px' } },
144
- h('button', {
145
- className: 'dshac-btn primary',
146
- onClick: applyUpdate,
147
- disabled: busy !== '' || !upd?.updateAvailable,
148
- title: upd?.updateAvailable ? '' : '先检查更新,有新版本才可应用',
149
- }, busy === 'apply' ? '更新中…' : '一键更新')),
150
- needRestart ? h('div', { className: 'dshac-note' }, '更新已写入 —— 重启 DeepSeek Harness 后生效。') : null,
151
- out ? h('div', { className: 'dshac-out' }, out) : null),
141
+ h('div', { className: 'dshac-row', style: { marginTop: '10px' } },
142
+ h('span', { className: 'dshac-muted' },
143
+ '当前 v' + (status?.version ?? upd?.current ?? '?') +
144
+ (upd ? ' · npm latest v' + (upd.latest ?? '—') : '') +
145
+ (upd?.mode ? '(' + (upd.mode === 'git' ? 'git 仓库' + (upd.gitClean === false ? ' · 有本地改动' : '') : 'npm 安装') + ')' : '')),
146
+ h('button', { className: 'dshac-btn primary', onClick: checkUpdate, disabled: busy !== '' },
147
+ busy === 'check' ? '检查中…' : '检查更新'),
148
+ h('button', {
149
+ className: 'dshac-btn',
150
+ onClick: applyUpdate,
151
+ disabled: busy !== '' || !upd?.updateAvailable,
152
+ title: upd?.updateAvailable ? '' : '有新版本时才可应用',
153
+ }, busy === 'apply' ? '更新中…' : '一键更新')),
154
+ needRestart ? h('div', { className: 'dshac-note' }, '更新已写入 —— 重启 DeepSeek Harness 后生效。') : null,
155
+ out ? h('div', { className: 'dshac-out' }, out) : null,
152
156
  );
153
157
  }
154
158
 
155
159
  return {
156
160
  inject: ['slots', 'locale'],
157
161
  apply(ctx) {
158
- ctx.effect(() => ctx.locale.register(NS, { zh: { panel: 'ArtCraft' }, en: { panel: 'ArtCraft' } }), 'artcraft: dictionaries');
162
+ ctx.effect(() => ctx.locale.register(NS, { zh: { artcraft: 'ArtCraft' }, en: { artcraft: 'ArtCraft' } }), 'artcraft: dictionaries');
159
163
  ctx.effect(() => {
160
164
  const el = document.createElement('style');
161
165
  el.id = 'dshac-styles';
@@ -163,18 +167,22 @@ window.__ModuleLoader__.load({
163
167
  document.head.appendChild(el);
164
168
  return () => el.remove();
165
169
  }, 'artcraft: styles');
166
- ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
167
- name: 'sidebar.panellist',
168
- id: PANEL_ID,
169
- order: 101,
170
- label: () => 'ArtCraft',
170
+ // 插件页 → 本插件详情卡:标题旁徽标(有新版本时)
171
+ ctx.slots.inject('plugins.detail.badge', () => ctx.slots.register({
172
+ name: 'plugins.detail.badge',
173
+ id: 'artcraft-update-badge',
174
+ order: 50,
175
+ label: () => 'ArtCraft 更新',
171
176
  locale: NS,
172
- }, PanelIcon));
173
- ctx.slots.inject('main', () => ctx.slots.register({
174
- name: 'main',
175
- key: PANEL_ID,
177
+ }, UpdateBadge));
178
+ // 插件页 → 本插件详情卡:内容区(工具链状态 + 检测更新 + 一键更新)
179
+ ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({
180
+ name: 'plugins.detail.section',
181
+ id: 'artcraft-status',
182
+ order: 50,
183
+ label: () => 'ArtCraft 工具链与更新',
176
184
  locale: NS,
177
- }, ArtCraftPage));
185
+ }, StatusSection));
178
186
  },
179
187
  };
180
188
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
- {
1
+ {
2
2
  "name": "@ninkch/dsh-artcraft",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Drive the open-source ArtCraft studio apps (PhotoCraft / FilmCraft / EffectCraft) from DeepSeek Harness — agent tools, a same-origin HTTP API, and a sidebar panel with toolchain status and self-update.",
6
6
  "keywords": [