@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 +57 -6
- package/dist/grid.html +33 -3
- package/dist/index.js +1 -1
- package/dist/index.min.js +8 -8
- package/package.json +1 -1
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="移除图片">×</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="移除文件">×</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 || '分析图片'}`
|
|
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
|
|
2065
|
-
|
|
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"
|
|
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
|
-
|
|
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=>({'&':'&','<':'<','>':'>','"':'"'}[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
|
-
|
|
213
|
+
// 用稳定标识映射回当前 id;找不到→空格(可下拉重选),不再加载死会话报「未找到」
|
|
214
|
+
for (const key of initial) addPane(resolveKey(key), true);
|
|
185
215
|
applyLayout();
|
|
186
216
|
})();
|
|
187
217
|
</script>
|