@wendongfly/myhi 1.0.118 → 1.0.120

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
@@ -94,6 +94,25 @@
94
94
  .btn-allow:hover { background: #2ea043; }
95
95
  .btn-deny { background: #21262d; color: #f85149; border: 1px solid #30363d !important; }
96
96
 
97
+ /* 权限弹窗 modal */
98
+ #perm-modal { display: none; position: fixed; inset: 0; z-index: 90; align-items: flex-end; justify-content: center; }
99
+ #perm-modal.open { display: flex; }
100
+ #perm-modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.55); }
101
+ #perm-modal-box { position: relative; background: #161b22; border-radius: 16px 16px 0 0; width: 100%; max-width: 480px; padding: 1rem 1rem; padding-bottom: max(1rem, env(safe-area-inset-bottom)); z-index: 1; animation: slideUp 0.25s ease; border-top: 3px solid #d29922; }
102
+ @keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
103
+ #perm-modal-box .pm-icon { text-align: center; font-size: 1.6rem; margin-bottom: 0.4rem; }
104
+ #perm-modal-box .pm-title { text-align: center; font-size: 0.95rem; font-weight: 600; color: #d29922; margin-bottom: 0.6rem; }
105
+ #perm-modal-box .pm-tool { text-align: center; font-size: 0.78rem; color: #8b949e; margin-bottom: 0.3rem; }
106
+ #perm-modal-box .pm-detail { background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 0.6rem 0.75rem; font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.78rem; line-height: 1.4; color: #e6edf3; white-space: pre-wrap; word-break: break-all; max-height: 200px; overflow-y: auto; margin-bottom: 0.75rem; }
107
+ #perm-modal-box .pm-actions { display: flex; gap: 0.5rem; }
108
+ #perm-modal-box .pm-actions button { flex: 1; padding: 0.6rem; border-radius: 10px; font-size: 0.88rem; font-weight: 600; cursor: pointer; border: none; transition: all 0.15s; }
109
+ #perm-modal-box .pm-actions button:active { transform: scale(0.96); }
110
+ #perm-modal-box .pm-btn-allow { background: #238636; color: #fff; }
111
+ #perm-modal-box .pm-btn-allow:hover { background: #2ea043; }
112
+ #perm-modal-box .pm-btn-deny { background: #21262d; color: #f85149; border: 1px solid #30363d; }
113
+ #perm-modal-box .pm-btn-deny:hover { background: #30363d; }
114
+ #perm-modal-box .pm-queue { text-align: center; font-size: 0.7rem; color: #8b949e; margin-top: 0.5rem; }
115
+
97
116
  /* Diff 视图 */
98
117
  .msg-diff { font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.78rem; background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 0.5rem 0.6rem; overflow-x: auto; line-height: 1.35; }
99
118
  .diff-add { color: #3fb950; background: rgba(46,160,67,0.1); }
@@ -226,12 +245,9 @@
226
245
  <button class="top-btn" onclick="openSettings()" title="设置">
227
246
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
228
247
  </button>
229
- <button class="top-btn" onclick="goBack()" title="返回">
230
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
231
- </button>
232
248
  <button class="top-btn" onclick="showUsage()" title="用量" style="font-size:0.7rem;color:var(--muted)">用量</button>
233
- <button class="top-btn" id="logout-btn" onclick="doLogout()" title="退出" style="display:none;color:#f0883e">
234
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
249
+ <button class="top-btn" onclick="goBack()" title="返回">
250
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
235
251
  </button>
236
252
  </div>
237
253
 
@@ -393,6 +409,22 @@
393
409
  </div>
394
410
  </div>
395
411
 
412
+ <!-- 权限授权弹窗 -->
413
+ <div id="perm-modal">
414
+ <div id="perm-modal-backdrop"></div>
415
+ <div id="perm-modal-box">
416
+ <div class="pm-icon">&#x1F6E1;&#xFE0F;</div>
417
+ <div class="pm-title" id="pm-title">请求权限</div>
418
+ <div class="pm-tool" id="pm-tool"></div>
419
+ <div class="pm-detail" id="pm-detail"></div>
420
+ <div class="pm-actions">
421
+ <button class="pm-btn-deny" id="pm-btn-deny">拒绝</button>
422
+ <button class="pm-btn-allow" id="pm-btn-allow">允许</button>
423
+ </div>
424
+ <div class="pm-queue" id="pm-queue" style="display:none"></div>
425
+ </div>
426
+ </div>
427
+
396
428
  <div id="status-overlay">连接中...</div>
397
429
 
398
430
  <script type="module">
@@ -400,10 +432,7 @@
400
432
  const ansi = new AnsiUp();
401
433
  ansi.use_classes = false;
402
434
 
403
- // 加载用户信息(独占模式显示退出按钮)
404
- fetch('/api/me').then(r => r.json()).then(data => {
405
- if (data.exclusive || data.hasUsers) document.getElementById('logout-btn').style.display = '';
406
- }).catch(() => {});
435
+ fetch('/api/me').then(r => r.json()).catch(() => {});
407
436
  window.showUsage = async () => {
408
437
  try {
409
438
  const res = await fetch('/api/usage');
@@ -453,13 +482,6 @@
453
482
  document.body.appendChild(overlay);
454
483
  } catch (err) { alert('获取用量失败: ' + err.message); }
455
484
  };
456
- window.doLogout = () => {
457
- if (!confirm('确定退出登录?')) return;
458
- fetch('/logout', { method: 'POST' }).finally(() => {
459
- socket.disconnect();
460
- location.href = '/login';
461
- });
462
- };
463
485
 
464
486
  // ── 状态 ──────────────────────────────────────
465
487
  const SESSION_ID = location.pathname.split('/').pop();
@@ -945,12 +967,83 @@
945
967
  if (actions) actions.innerHTML = answer === 'y' ? '<span style="color:#3fb950">已允许</span>' : '<span style="color:#f85149">已拒绝</span>';
946
968
  };
947
969
 
970
+ // ── 权限弹窗队列管理 ──────────────────────────────
971
+ const permQueue = []; // { reqId, toolName, detail }
972
+ let permModalOpen = false;
973
+
974
+ function showPermModal(item) {
975
+ const modal = document.getElementById('perm-modal');
976
+ document.getElementById('pm-title').textContent = `${item.toolName} 请求权限`;
977
+ document.getElementById('pm-tool').textContent = item.toolLabel || '';
978
+ document.getElementById('pm-detail').textContent = item.detail;
979
+ const queueEl = document.getElementById('pm-queue');
980
+ if (permQueue.length > 0) {
981
+ queueEl.textContent = `还有 ${permQueue.length} 个权限请求等待处理`;
982
+ queueEl.style.display = '';
983
+ } else {
984
+ queueEl.style.display = 'none';
985
+ }
986
+ // 绑定按钮
987
+ document.getElementById('pm-btn-allow').onclick = () => respondPermModal(item.reqId, true);
988
+ document.getElementById('pm-btn-deny').onclick = () => respondPermModal(item.reqId, false);
989
+ modal.classList.add('open');
990
+ permModalOpen = true;
991
+ // 振动提醒(移动端)
992
+ if (navigator.vibrate) navigator.vibrate(100);
993
+ }
994
+
995
+ function closePermModal() {
996
+ document.getElementById('perm-modal').classList.remove('open');
997
+ permModalOpen = false;
998
+ }
999
+
1000
+ function respondPermModal(requestId, allow) {
1001
+ if (!isController) { addStatusMessage('你没有控制权,无法操作'); return; }
1002
+ socket.emit('agent:permission', { requestId, allow });
1003
+ // 更新内联卡片
1004
+ const inlineActions = document.querySelector(`.perm-actions[data-reqid="${requestId}"]`);
1005
+ if (inlineActions) inlineActions.innerHTML = allow ? '<span style="color:#3fb950">✓ 已允许</span>' : '<span style="color:#f85149">✗ 已拒绝</span>';
1006
+ closePermModal();
1007
+ setWorkState('working');
1008
+ // 处理队列中的下一个
1009
+ if (permQueue.length > 0) {
1010
+ const next = permQueue.shift();
1011
+ setTimeout(() => showPermModal(next), 200);
1012
+ }
1013
+ }
1014
+
1015
+ function enqueuePermission(reqId, toolName, detail, toolLabel) {
1016
+ const item = { reqId, toolName, detail, toolLabel };
1017
+ if (!permModalOpen) {
1018
+ showPermModal(item);
1019
+ } else {
1020
+ permQueue.push(item);
1021
+ // 更新队列提示
1022
+ const queueEl = document.getElementById('pm-queue');
1023
+ queueEl.textContent = `还有 ${permQueue.length} 个权限请求等待处理`;
1024
+ queueEl.style.display = '';
1025
+ }
1026
+ }
1027
+
1028
+ // backdrop 点击不关闭弹窗(防止误触),但可以拒绝
1029
+ document.getElementById('perm-modal-backdrop').addEventListener('click', () => {
1030
+ // 不自动关闭,必须明确选择
1031
+ });
1032
+
948
1033
  window.respondAgentPermission = function(requestId, allow, btn) {
949
1034
  if (!isController) { addStatusMessage('你没有控制权,无法操作'); return; }
950
1035
  socket.emit('agent:permission', { requestId, allow });
951
1036
  const actions = btn.closest('.perm-actions');
952
- if (actions) actions.innerHTML = allow ? '<span style="color:#3fb950">已允许</span>' : '<span style="color:#f85149">已拒绝</span>';
1037
+ if (actions) actions.innerHTML = allow ? '<span style="color:#3fb950">✓ 已允许</span>' : '<span style="color:#f85149">✗ 已拒绝</span>';
953
1038
  setWorkState('working');
1039
+ // 如果弹窗正在显示同一个请求,也关闭它
1040
+ if (permModalOpen) {
1041
+ closePermModal();
1042
+ if (permQueue.length > 0) {
1043
+ const next = permQueue.shift();
1044
+ setTimeout(() => showPermModal(next), 200);
1045
+ }
1046
+ }
954
1047
  };
955
1048
 
956
1049
  // ── 输出处理 ──────────────────────────────────
@@ -1176,10 +1269,15 @@
1176
1269
  const toolName = msg.request.tool_name || '未知工具';
1177
1270
  const input = msg.request.input || {};
1178
1271
  const detail = toolName === 'Bash' ? (input.command || JSON.stringify(input))
1179
- : toolName === 'Edit' ? `${input.file_path || ''}`
1272
+ : toolName === 'Edit' ? `${input.file_path || ''}\n${input.old_string ? '替换: ' + input.old_string.slice(0, 120) : ''}`
1180
1273
  : toolName === 'Write' ? `${input.file_path || ''}`
1274
+ : toolName === 'Read' ? `${input.file_path || ''}`
1181
1275
  : JSON.stringify(input, null, 2);
1182
1276
  const reqId = msg.request_id || msg.request?.request_id || msg.uuid;
1277
+ // 工具类型标签
1278
+ const toolLabels = { Bash: '执行命令', Edit: '编辑文件', Write: '写入文件', Read: '读取文件', Glob: '搜索文件', Grep: '搜索内容' };
1279
+ const toolLabel = toolLabels[toolName] || toolName;
1280
+ // 内联卡片
1183
1281
  const el = document.createElement('div');
1184
1282
  el.className = 'msg msg-permission';
1185
1283
  el.innerHTML = `<div class="perm-title">${escHtml(toolName)} 请求权限</div><div class="perm-detail">${escHtml(detail)}</div>
@@ -1191,6 +1289,8 @@
1191
1289
  trimMessages();
1192
1290
  scrollToBottom();
1193
1291
  setWorkState('waiting');
1292
+ // 弹窗授权
1293
+ enqueuePermission(reqId, toolName, detail, toolLabel);
1194
1294
  }
1195
1295
  break;
1196
1296
  case 'result':
package/dist/package.json CHANGED
@@ -1 +1 @@
1
- {"type":"module","version":"1.0.118"}
1
+ {"type":"module","version":"1.0.120"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wendongfly/myhi",
3
- "version": "1.0.118",
3
+ "version": "1.0.120",
4
4
  "description": "Web-based terminal sharing with chat UI — control your terminal from phone via LAN/Tailscale",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",