@wendongfly/myhi 1.3.114 → 1.3.116

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/chat.html CHANGED
@@ -449,6 +449,12 @@
449
449
  <button class="fp-btn" onclick="fpExitSync()">退出同步</button>
450
450
  </div>
451
451
  <div class="fp-path" id="fp-path"></div>
452
+ <div id="fp-tools" style="display:flex;gap:0.4rem;align-items:center;padding:0.3rem 0.75rem;flex-wrap:wrap">
453
+ <button class="fp-btn" onclick="fpOpenFolder()" title="在【myhi 服务端那台机器】的桌面打开系统文件夹——只有你就坐在跑 myhi 的机器前才看得到;远程访问就用下方列表浏览/下载">📂 服务端本机打开</button>
454
+ <button class="fp-btn" onclick="fpOpenEditor()" title="在服务端本机用 VS Code 打开(需装 code 命令)">VS Code(服务端)</button>
455
+ <span style="flex:1"></span>
456
+ <button class="fp-btn" id="fp-sort-btn" onclick="fpToggleSort()" title="切换排序">排序: 名称</button>
457
+ </div>
452
458
  <div class="fp-list" id="fp-list"></div>
453
459
  <div class="fp-diff-actions" id="fp-diff-actions" style="display:none">
454
460
  <button class="fp-btn fp-btn-primary" onclick="fpSyncToLocal()">下载到本地</button>
@@ -1583,7 +1589,20 @@
1583
1589
  cancelBtn.classList.toggle('show', busy);
1584
1590
  sendBtn.style.display = busy ? 'none' : '';
1585
1591
  }
1592
+ _postStatus(state);
1593
+ }
1594
+ // 内嵌分屏(iframe)时把状态上报给外层 grid,更新窗格状态灯
1595
+ function _postStatus(state) {
1596
+ if (window.parent && window.parent !== window) {
1597
+ try { window.parent.postMessage({ type: 'myhi-status', sessionId: SESSION_ID, state }, '*'); } catch {}
1598
+ }
1586
1599
  }
1600
+ // 接收外层 grid 的命令(分屏里顶栏被 embed 隐藏,靠外层按钮驱动本页功能)
1601
+ window.addEventListener('message', (e) => {
1602
+ const m = e.data;
1603
+ if (!m || m.type !== 'myhi-cmd') return;
1604
+ if (m.cmd === 'open-files' && typeof openFilePanel === 'function') openFilePanel();
1605
+ });
1587
1606
 
1588
1607
  function updateShortcutBar() {
1589
1608
  if (!currentSession) { shortcutBar.style.display = 'none'; return; }
@@ -2196,6 +2215,27 @@
2196
2215
 
2197
2216
  // ── 文件管理 ──────────────────────────────────
2198
2217
  let fpCurrentPath = '';
2218
+ let fpSort = 'name'; // name | recent
2219
+ window.fpToggleSort = function() {
2220
+ fpSort = fpSort === 'name' ? 'recent' : 'name';
2221
+ const b = document.getElementById('fp-sort-btn');
2222
+ if (b) b.textContent = '排序: ' + (fpSort === 'recent' ? '最近修改' : '名称');
2223
+ fpLoadFiles();
2224
+ };
2225
+ window.fpOpenFolder = async function() {
2226
+ try {
2227
+ const r = await fetch(`/api/session/${SESSION_ID}/open-folder`, { method: 'POST' });
2228
+ const d = await r.json();
2229
+ addStatusMessage(d.ok ? '已在服务端机器打开文件夹(远程访问则看不到,用下方列表浏览/下载)' : ('打开失败: ' + (d.error || '')));
2230
+ } catch (e) { addStatusMessage('打开出错: ' + e.message); }
2231
+ };
2232
+ window.fpOpenEditor = async function() {
2233
+ try {
2234
+ const r = await fetch(`/api/session/${SESSION_ID}/open-editor`, { method: 'POST' });
2235
+ const d = await r.json();
2236
+ addStatusMessage(d.ok ? '已尝试用 VS Code 打开(需服务端装了 code 命令且你在本机)' : ('打开失败: ' + (d.error || '')));
2237
+ } catch (e) { addStatusMessage('打开出错: ' + e.message); }
2238
+ };
2199
2239
  window.openFilePanel = function() {
2200
2240
  fpCurrentPath = '';
2201
2241
  fpLoadFiles();
@@ -2223,7 +2263,10 @@
2223
2263
  if (data.parent != null) {
2224
2264
  html += `<div class="fp-item" onclick="fpNav('${escHtml(data.parent)}')"><span class="fp-icon">📁</span><span class="fp-name">..</span></div>`;
2225
2265
  }
2226
- for (const e of data.entries) {
2266
+ const entries = (data.entries || []).slice();
2267
+ if (fpSort === 'recent') entries.sort((a, b) => (b.mtime || 0) - (a.mtime || 0)); // 最近修改在前,看 AI 刚生成啥
2268
+ else entries.sort((a, b) => (Number(b.isDir) - Number(a.isDir)) || a.name.localeCompare(b.name));
2269
+ for (const e of entries) {
2227
2270
  const rel = fpCurrentPath ? fpCurrentPath + '/' + e.name : e.name;
2228
2271
  if (e.isDir) {
2229
2272
  html += `<div class="fp-item" onclick="fpNav('${escHtml(rel)}')"><span class="fp-icon">📁</span><span class="fp-name">${escHtml(e.name)}</span></div>`;
@@ -2740,6 +2783,7 @@
2740
2783
  }
2741
2784
  if (!_askQuestions.length) return;
2742
2785
  _renderAskSheet();
2786
+ _postStatus('waiting'); // 通知外层 grid:本格在等你回答
2743
2787
  };
2744
2788
 
2745
2789
  function _renderAskSheet() {
package/dist/grid.html CHANGED
@@ -47,6 +47,12 @@
47
47
  .pane-head select { flex:1; min-width:0; background:var(--bg); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.28rem 0.4rem; font-size:0.78rem; }
48
48
  .pane-head .pane-picker { flex:1; min-width:0; display:flex; align-items:center; justify-content:space-between; gap:0.3rem; text-align:left; }
49
49
  #tp-list [data-id]:hover { background:var(--surface2) !important; }
50
+ /* 窗格状态灯 */
51
+ .pane-dot { width:9px; height:9px; border-radius:50%; background:var(--dim); flex-shrink:0; }
52
+ .pane-dot.pulse { animation:panepulse 1.1s ease-in-out infinite; }
53
+ @keyframes panepulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
54
+ .pane-head.flash { animation:paneflash 1s ease-in-out 3; }
55
+ @keyframes paneflash { 0%,100%{background:var(--surface2)} 50%{background:var(--blue2)} }
50
56
  .pane-head button { background:var(--bg); border:1px solid var(--border); color:var(--muted); border-radius:6px; padding:0.2rem 0.45rem; font-size:0.8rem; cursor:pointer; line-height:1; }
51
57
  .pane-head button:hover { color:var(--text); border-color:var(--blue2); }
52
58
  .pane-body { flex:1; min-height:0; position:relative; }
@@ -67,6 +73,7 @@
67
73
  <button data-lo="3">3列</button>
68
74
  </div>
69
75
  <span class="spacer"></span>
76
+ <button class="bar-btn" id="notify-btn" onclick="toggleNotify()" title="任务完成/待回答时桌面通知+提示音(默认关)">🔔 关</button>
70
77
  <button class="bar-btn" onclick="refreshSessions(true)" title="刷新任务列表">↻</button>
71
78
  <button class="bar-btn" onclick="addPane('')" title="加一个空窗格,从下拉选已有任务">+ 窗格</button>
72
79
  <button class="bar-btn" style="border-color:var(--green);color:var(--green)" onclick="openNewTask()" title="新建任务(和首页一致:预设/名称/命令/目录)">🆕 新建任务</button>
@@ -174,10 +181,17 @@
174
181
  function addPane(sid, skipSave) {
175
182
  const pane = document.createElement('div'); pane.className = 'pane'; pane._sid = sid || '';
176
183
  const head = document.createElement('div'); head.className = 'pane-head';
184
+ const dot = document.createElement('span'); dot.className = 'pane-dot'; dot.title = '任务状态';
177
185
  const picker = document.createElement('button'); picker.className = 'pane-picker';
186
+ const files = document.createElement('button'); files.textContent='📁'; files.title='查看该任务的文件(在本格内浏览/下载,远程也能用)';
187
+ files.addEventListener('click', () => {
188
+ if (pane._iframe && pane._iframe.contentWindow) pane._iframe.contentWindow.postMessage({ type:'myhi-cmd', cmd:'open-files' }, '*');
189
+ else alert('先给这个格子选一个任务');
190
+ });
178
191
  const full = document.createElement('button'); full.textContent='↗'; full.title='在新标签页打开';
179
192
  const close = document.createElement('button'); close.textContent='✕'; close.title='关闭此格'; close.style.color='var(--red)';
180
- head.appendChild(picker); head.appendChild(full); head.appendChild(close);
193
+ head.appendChild(dot); head.appendChild(picker); head.appendChild(files); head.appendChild(full); head.appendChild(close);
194
+ pane._dot = dot; pane._head = head;
181
195
  const body = document.createElement('div'); body.className = 'pane-body';
182
196
  const empty = document.createElement('div'); empty.className='pane-empty'; empty.textContent='点上方「选择任务」挑一个(可搜索/分组/按标签)'; body.appendChild(empty);
183
197
  pane.appendChild(head); pane.appendChild(body);
@@ -350,6 +364,44 @@
350
364
  document.getElementById('nt-cwd').value = b.dataset.dir;
351
365
  });
352
366
 
367
+ // ── 窗格状态灯 + 完成/待回答高亮 + 通知(内嵌会话页 postMessage 上报)──
368
+ let _notify = localStorage.getItem('myhi_grid_notify') === '1';
369
+ function syncNotifyBtn(){ const b=document.getElementById('notify-btn'); if(b) b.textContent='🔔 '+(_notify?'开':'关'); }
370
+ window.toggleNotify = function(){
371
+ _notify = !_notify;
372
+ if (_notify && 'Notification' in window && Notification.permission==='default') Notification.requestPermission();
373
+ localStorage.setItem('myhi_grid_notify', _notify?'1':'0');
374
+ syncNotifyBtn();
375
+ };
376
+ syncNotifyBtn();
377
+ function paneTitle(pane){ const s = pane._sid ? sessById(pane._sid) : null; return s ? sName(s) : '任务'; }
378
+ function beep(){ try { const c=new (window.AudioContext||window.webkitAudioContext)(); const o=c.createOscillator(), g=c.createGain(); o.connect(g); g.connect(c.destination); o.frequency.value=660; g.gain.value=0.06; o.start(); setTimeout(()=>{o.stop();c.close();},180);} catch {} }
379
+ function fireNotify(pane,label){
380
+ if(!_notify) return;
381
+ try { if('Notification' in window && Notification.permission==='granted') new Notification('myhi · '+label,{body:paneTitle(pane)}); } catch {}
382
+ beep();
383
+ }
384
+ function setPaneStatus(pane,state){
385
+ const prev=pane._state; pane._state=state;
386
+ if(pane._dot){
387
+ const c={idle:'var(--green)',working:'var(--blue)',thinking:'var(--blue)',waiting:'#d29922'}[state]||'var(--dim)';
388
+ pane._dot.style.background=c;
389
+ pane._dot.classList.toggle('pulse', state==='working'||state==='thinking');
390
+ pane._dot.title={idle:'空闲/已完成',working:'执行中',thinking:'执行中',waiting:'等你回答'}[state]||'状态';
391
+ }
392
+ const justDone=(prev==='working'||prev==='thinking')&&state==='idle';
393
+ if(justDone||state==='waiting'){
394
+ if(pane._head){ pane._head.classList.remove('flash'); void pane._head.offsetWidth; pane._head.classList.add('flash'); setTimeout(()=>pane._head&&pane._head.classList.remove('flash'),3200); }
395
+ fireNotify(pane, state==='waiting'?'待回答':'已完成');
396
+ }
397
+ }
398
+ window.addEventListener('message',(e)=>{
399
+ const m=e.data;
400
+ if(!m||m.type!=='myhi-status') return;
401
+ const pane=paneEls.find(p=>p._sid&&p._sid===m.sessionId);
402
+ if(pane) setPaneStatus(pane,m.state);
403
+ });
404
+
353
405
  // ── 初始化 ──
354
406
  (async function init() {
355
407
  await refreshSessions(false);