@wendongfly/myhi 1.3.108 → 1.3.110

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
@@ -331,6 +331,7 @@
331
331
  <body>
332
332
 
333
333
  <div id="top-bar">
334
+ <button class="top-btn" onclick="location.href='/grid'" title="分屏工作台(多任务并排)" style="font-size:1.05rem;padding:0 0.35rem;color:var(--blue2)">▦</button>
334
335
  <div id="title-group">
335
336
  <div id="session-title">加载中...</div>
336
337
  <div id="session-cwd"></div>
@@ -341,7 +342,6 @@
341
342
  <button class="top-btn" onclick="showUsage()" title="用量" style="font-size:0.7rem;color:var(--muted)">用量</button>
342
343
  <button class="top-btn" onclick="openFilePanel()" title="文件" style="font-size:0.7rem;color:var(--muted)">文件</button>
343
344
  <button class="top-btn" onclick="openSwitchSheet()" title="切换会话" style="font-size:0.7rem;color:var(--muted)">切换</button>
344
- <button class="top-btn" onclick="location.href='/grid'" title="分屏工作台(多任务并排)" style="font-size:0.7rem;color:var(--muted)">▦ 分屏</button>
345
345
  <button class="top-btn" onclick="goBack()" title="返回">
346
346
  <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>
347
347
  </button>
@@ -365,11 +365,21 @@
365
365
  <span class="img-info">已添加图片,输入文字后发送</span>
366
366
  <button class="img-remove" onclick="clearPendingImage()" title="移除图片">&times;</button>
367
367
  </div>
368
+ <div id="file-preview" style="display:none;align-items:center;gap:0.5rem;padding:0.5rem 0.75rem;border-bottom:1px solid var(--surface2);background:var(--surface)">
369
+ <span style="font-size:1.1rem;flex-shrink:0">📎</span>
370
+ <span id="file-preview-name" style="flex:1;font-size:0.78rem;color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></span>
371
+ <span style="font-size:0.66rem;color:var(--muted);flex-shrink:0">临时·仅本会话分析</span>
372
+ <button class="img-remove" onclick="clearPendingFile()" title="移除文件">&times;</button>
373
+ </div>
368
374
  <textarea id="cmd-input" rows="1" placeholder="输入消息..." autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
369
375
  <div id="input-toolbar">
370
376
  <button class="tb-btn" id="btn-photo" onclick="openCamera()" title="拍照">
371
377
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/><circle cx="12" cy="13" r="4"/></svg>
372
378
  </button>
379
+ <button class="tb-btn" id="btn-file" onclick="document.getElementById('file-input').click()" title="上传临时文件让 AI 分析(不进任务文件树,仅本会话)">
380
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/></svg>
381
+ </button>
382
+ <input type="file" id="file-input" style="display:none" onchange="uploadTempFile(this.files[0]); this.value='';">
373
383
  <span id="enter-hint" style="display:none;font-size:0.68rem;color:var(--muted);white-space:nowrap;align-self:center;margin-left:0.15rem"></span>
374
384
  <div class="spacer"></div>
375
385
  <button class="tb-btn" id="voice-btn" onclick="openVaSheet()" title="语音输入 / 需求助手">
@@ -2036,11 +2046,44 @@
2036
2046
  cmdInput.focus();
2037
2047
  }
2038
2048
 
2049
+ // ── 待发送临时文件(存 ~/.myhi/uploads/,**不进项目文件树**,仅本会话即时分析)──
2050
+ let _pendingFile = null; // { path, name }
2051
+ window.clearPendingFile = function() {
2052
+ _pendingFile = null;
2053
+ const fp = document.getElementById('file-preview');
2054
+ if (fp) fp.style.display = 'none';
2055
+ };
2056
+ window.uploadTempFile = async function(file) {
2057
+ if (!file) return;
2058
+ const fp = document.getElementById('file-preview');
2059
+ const nameEl = document.getElementById('file-preview-name');
2060
+ if (nameEl) nameEl.textContent = '上传中… ' + file.name;
2061
+ if (fp) fp.style.display = 'flex';
2062
+ try {
2063
+ const form = new FormData();
2064
+ form.append('file', file, file.name);
2065
+ const r = await fetch(`/upload-file?sessionId=${SESSION_ID}`, { method: 'POST', body: form });
2066
+ const d = await r.json();
2067
+ if (d.path) {
2068
+ _pendingFile = { path: d.path, name: d.name || file.name };
2069
+ if (nameEl) nameEl.textContent = _pendingFile.name;
2070
+ cmdInput.placeholder = '输入分析要求后发送(可留空)…';
2071
+ cmdInput.focus();
2072
+ } else {
2073
+ if (nameEl) nameEl.textContent = '上传失败: ' + (d.error || '未知');
2074
+ _pendingFile = null;
2075
+ }
2076
+ } catch (e) {
2077
+ if (nameEl) nameEl.textContent = '上传出错: ' + e.message;
2078
+ _pendingFile = null;
2079
+ }
2080
+ };
2081
+
2039
2082
  // ── 输入处理 ──────────────────────────────────
2040
2083
  window.sendCommand = sendCommand;
2041
2084
  function sendCommand() {
2042
2085
  const text = cmdInput.value.trim();
2043
- if (!text && !_pendingImage) return;
2086
+ if (!text && !_pendingImage && !_pendingFile) return;
2044
2087
 
2045
2088
  if (!isController) {
2046
2089
  if (canTakeControl()) { socket.emit('take-control', { sessionId: SESSION_ID }); }
@@ -2049,29 +2092,37 @@
2049
2092
  endStream();
2050
2093
 
2051
2094
  const imgPath = _pendingImage?.path || null;
2095
+ const filePath = _pendingFile?.path || null;
2096
+ const fileName = _pendingFile?.name || '';
2052
2097
  const userText = text;
2053
- const displayText = imgPath ? `📷 ${userText || '分析图片'}` : text;
2098
+ const displayText = imgPath ? `📷 ${userText || '分析图片'}`
2099
+ : filePath ? `📎 ${fileName}${userText ? ' ' + userText : ''}`
2100
+ : text;
2054
2101
 
2055
2102
  addInputMessage(displayText);
2056
2103
 
2057
2104
  if (currentSession?.mode === 'agent') {
2058
2105
  const prompt = imgPath
2059
2106
  ? `用户上传了一张图片,路径是 ${imgPath},请用 Read 工具读取这张图片。用户的要求是:${userText || '描述图片内容'}`
2107
+ : filePath
2108
+ ? `用户上传了一个临时文件用于本次分析(不在项目文件树里),路径是 ${filePath}(原文件名 ${fileName}),请用合适的工具(如 Read)读取并分析它。用户的要求是:${userText || '分析这个文件'}`
2060
2109
  : text;
2061
2110
  socket.emit('agent:query', { prompt });
2062
2111
  showThinking();
2063
2112
  } else {
2064
- const input = imgPath
2065
- ? (userText ? `${userText} ${imgPath}` : imgPath)
2113
+ const attach = imgPath || filePath;
2114
+ const input = attach
2115
+ ? (userText ? `${userText} ${attach}` : attach)
2066
2116
  : text;
2067
2117
  socket.emit('input', input + '\r');
2068
2118
  }
2069
- cmdHistory.unshift(imgPath ? `[图片] ${userText}` : text);
2119
+ cmdHistory.unshift(imgPath ? `[图片] ${userText}` : filePath ? `[文件] ${fileName} ${userText}` : text);
2070
2120
  if (cmdHistory.length > 100) cmdHistory.pop();
2071
2121
  historyIdx = -1;
2072
2122
  cmdInput.value = '';
2073
2123
  cmdInput.style.height = 'auto';
2074
2124
  clearPendingImage();
2125
+ clearPendingFile();
2075
2126
  cmdInput.focus();
2076
2127
  }
2077
2128
 
package/dist/grid.html CHANGED
@@ -5,6 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
6
6
  <meta name="theme-color" content="#0d1117">
7
7
  <title>myhi 分屏</title>
8
+ <script src="/sio-client/socket.io.min.js"></script>
8
9
  <script>
9
10
  (function(){try{
10
11
  var t=localStorage.getItem('myhi_theme')||'dark';
@@ -65,7 +66,8 @@
65
66
  </div>
66
67
  <span class="spacer"></span>
67
68
  <button class="bar-btn" onclick="refreshSessions(true)" title="刷新任务列表">↻</button>
68
- <button class="bar-btn" style="border-color:var(--blue2);color:var(--blue)" onclick="addPane('')">+ 添加任务</button>
69
+ <button class="bar-btn" onclick="addPane('')" title="加一个空窗格,从下拉选已有任务">+ 窗格</button>
70
+ <button class="bar-btn" style="border-color:var(--green);color:var(--green)" onclick="newTask()" title="新建一个任务并放进新窗格">🆕 新建任务</button>
69
71
  </div>
70
72
  <div id="grid"></div>
71
73
  <div id="empty-hint" style="display:none">点「+ 添加任务」把多个任务并排看。<br>拉宽窗口自动多几格,或用上方按钮锁定布局。</div>
@@ -83,12 +85,24 @@
83
85
  return { layout: 'auto', panes: [] };
84
86
  }
85
87
  function saveState() {
86
- state.panes = paneEls.map(p => p._sid || '');
88
+ // 存【稳定标识】而不是易变 id——myhi 会话 id 每次重启都重新 randomUUID,存 id 重启后必失效
89
+ state.panes = paneEls.map(p => p._key || null);
87
90
  localStorage.setItem(LS_KEY, JSON.stringify(state));
88
91
  }
89
92
  function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c])); }
90
93
  function sName(s){ return s.title || s.name || s.summary || (s.id||s.sessionId||'').slice(0,12) || '未命名'; }
91
94
  function sId(s){ return s.id || s.sessionId; }
95
+ function sessById(id){ return sessions.find(s => sId(s) === id); }
96
+ // 稳定标识:claudeSessionId(agent 会话,--resume 用,重启不变) / createdAt(持久化,稳定)
97
+ function keyOf(s){ return s ? { cid: s.claudeSessionId || null, createdAt: s.createdAt || null, cwd: s.cwd || null } : null; }
98
+ // 把稳定标识映射回【当前】会话 id(重启后 id 变了也能找回同一个会话);找不到返回 ''
99
+ function resolveKey(key){
100
+ if (!key) return '';
101
+ if (typeof key === 'string') return sessById(key) ? key : ''; // 兼容旧格式(曾直接存 id)
102
+ for (const s of sessions) if (key.cid && s.claudeSessionId && s.claudeSessionId === key.cid) return sId(s);
103
+ for (const s of sessions) if (key.createdAt && s.createdAt === key.createdAt && (!key.cwd || s.cwd === key.cwd)) return sId(s);
104
+ return '';
105
+ }
92
106
 
93
107
  // ── 布局 ──
94
108
  function applyLayout() {
@@ -142,6 +156,7 @@
142
156
  }
143
157
  function setPaneSession(pane, sid, keepSelect) {
144
158
  pane._sid = sid || '';
159
+ pane._key = sid ? keyOf(sessById(sid)) : null; // 记住稳定标识,供重启后找回
145
160
  if (!keepSelect) pane._sel.value = sid || '';
146
161
  // 只重建这一格的 iframe,其它格不受影响
147
162
  if (pane._iframe) { pane._iframe.remove(); pane._iframe = null; }
@@ -177,11 +192,26 @@
177
192
  }
178
193
  }
179
194
 
195
+ // ── 新建任务(走 socket create-agent,和首页一致)──
196
+ const socket = io();
197
+ window.newTask = function() {
198
+ const title = (prompt('新任务名称:') || '').trim();
199
+ if (!title) return;
200
+ socket.emit('create-agent', { title }, (res) => {
201
+ if (res && res.ok && res.session) {
202
+ refreshSessions(true).then(() => addPane(res.session.id));
203
+ } else {
204
+ alert('创建失败:' + ((res && res.error) || '未知错误'));
205
+ }
206
+ });
207
+ };
208
+
180
209
  // ── 初始化 ──
181
210
  (async function init() {
182
211
  await refreshSessions(false);
183
212
  const initial = (state.panes && state.panes.length) ? state.panes : [];
184
- for (const sid of initial) addPane(sid, true);
213
+ // 用稳定标识映射回当前 id;找不到→空格(可下拉重选),不再加载死会话报「未找到」
214
+ for (const key of initial) addPane(resolveKey(key), true);
185
215
  applyLayout();
186
216
  })();
187
217
  </script>