@wendongfly/myhi 1.3.132 → 1.3.138
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 +315 -15
- package/dist/index.html +1501 -1294
- package/dist/index.js +1 -1
- package/dist/index.min.js +145 -145
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -153,6 +153,11 @@
|
|
|
153
153
|
.sub-act .k { color: var(--muted); }
|
|
154
154
|
.sub-summary { background: var(--surface); border: 1px solid var(--surface2); border-radius: 6px; padding: 0.4rem 0.5rem; margin-top: 0.35rem; font-size: 0.75rem; white-space: pre-wrap; word-break: break-word; max-height: 260px; overflow-y: auto; }
|
|
155
155
|
|
|
156
|
+
/* 往上翻看历史时来了新消息 → 底部浮动「↓ 新消息」按钮,点击回到底部 */
|
|
157
|
+
#new-msg-btn { position: fixed; left: 50%; transform: translateX(-50%); bottom: 92px; z-index: 60; background: var(--blue2, #4a9eff); color: #fff; border: none; border-radius: 16px; padding: 0.35rem 0.95rem; font-size: 0.78rem; font-weight: 600; cursor: pointer; box-shadow: 0 3px 10px rgba(0,0,0,0.35); display: none; }
|
|
158
|
+
#new-msg-btn.show { display: block; }
|
|
159
|
+
#new-msg-btn:active { opacity: 0.8; }
|
|
160
|
+
|
|
156
161
|
|
|
157
162
|
/* Diff 视图 */
|
|
158
163
|
.msg-diff { font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.78rem; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 0.5rem 0.6rem; overflow-x: auto; line-height: 1.35; }
|
|
@@ -384,6 +389,12 @@
|
|
|
384
389
|
<div id="input-area">
|
|
385
390
|
<div id="readonly-overlay" style="display:none"></div>
|
|
386
391
|
<div id="input-box">
|
|
392
|
+
<!-- 待发草稿提示:本轮干完(idle)且收件箱里还有本任务的草稿时浮出来 -->
|
|
393
|
+
<div id="draft-tip" style="display:none;align-items:center;gap:0.5rem;padding:0.4rem 0.75rem;border-bottom:1px solid var(--surface2);background:rgba(210,153,34,0.10);cursor:pointer" onclick="openInboxSheet()">
|
|
394
|
+
<span style="font-size:0.95rem;flex-shrink:0">📥</span>
|
|
395
|
+
<span id="draft-tip-text" style="flex:1;font-size:0.74rem;color:var(--yellow);overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></span>
|
|
396
|
+
<button class="img-remove" onclick="event.stopPropagation();hideDraftTip()" title="暂不处理">×</button>
|
|
397
|
+
</div>
|
|
387
398
|
<div id="img-preview">
|
|
388
399
|
<img id="img-preview-thumb" src="" alt="">
|
|
389
400
|
<span class="img-info">已添加图片,输入文字后发送</span>
|
|
@@ -412,6 +423,10 @@
|
|
|
412
423
|
<button class="tb-btn" id="send-btn" onclick="sendCommand()" title="发送">
|
|
413
424
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
|
414
425
|
</button>
|
|
426
|
+
<!-- AI 忙时顶替发送键:这轮还没完(可能在跑子代理),先把想法存成草稿 -->
|
|
427
|
+
<button class="tb-btn" id="draft-btn" style="display:none;color:var(--yellow)" onclick="saveDraftFromInput('busy')" title="AI 还在跑,先存为草稿,干完再发">
|
|
428
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
429
|
+
</button>
|
|
415
430
|
<button class="tb-btn" id="cancel-btn" onclick="cancelAgent()" title="取消 (Esc)">
|
|
416
431
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>
|
|
417
432
|
</button>
|
|
@@ -448,6 +463,7 @@
|
|
|
448
463
|
<button class="sk sk-claude" onclick="openSecretsSheet()" style="color:var(--blue2)">🔑 密钥</button>
|
|
449
464
|
<button class="sk sk-claude" onclick="openGuardSheet()" style="color:var(--blue2)">🛡 护栏</button>
|
|
450
465
|
<button class="sk sk-claude" onclick="openFollowUpSheet()" style="color:var(--blue2)">🔄 跟进</button>
|
|
466
|
+
<button class="sk sk-claude" id="sk-inbox" onclick="openInboxSheet()" style="color:var(--yellow)">📥 草稿<span id="inbox-badge" style="display:none;margin-left:0.2rem;background:var(--yellow);color:#000;border-radius:8px;padding:0 0.32rem;font-size:0.66rem;font-weight:700"></span></button>
|
|
451
467
|
<button class="sk sk-claude" onclick="openGitSheet()" style="color:var(--green)">提交</button>
|
|
452
468
|
<button class="sk sk-claude" onclick="showMemory()">记忆</button>
|
|
453
469
|
<button class="sk sk-claude" onclick="openVaSheet()" style="color:var(--blue2)">语音</button>
|
|
@@ -550,12 +566,22 @@
|
|
|
550
566
|
</div>
|
|
551
567
|
</div>
|
|
552
568
|
|
|
569
|
+
<!-- 往上翻历史时来新消息的浮动提示,点击回到底部 -->
|
|
570
|
+
<button id="new-msg-btn" onclick="jumpToBottom()">↓ 新消息</button>
|
|
571
|
+
|
|
553
572
|
<!-- 模型选择面板 -->
|
|
554
573
|
<div id="model-sheet" class="action-sheet">
|
|
555
574
|
<div class="action-sheet-backdrop" onclick="closeModelSheet()"></div>
|
|
556
575
|
<div class="action-sheet-box">
|
|
557
576
|
<div class="action-sheet-title">选择模型</div>
|
|
558
577
|
<div id="model-list"></div>
|
|
578
|
+
<div style="border-top:1px solid var(--surface2);margin-top:0.4rem;padding-top:0.5rem">
|
|
579
|
+
<div style="font-size:0.7rem;color:var(--muted);padding:0 0.2rem 0.35rem;line-height:1.5">自定义/指定模型:输入别名或全名(如 <code>opus</code> / <code>haiku</code> / <code>claude-sonnet-4-6</code>),claude 出新模型也能直接指定。</div>
|
|
580
|
+
<div style="display:flex;gap:0.4rem">
|
|
581
|
+
<input id="model-custom" class="slash-inp" placeholder="输入模型名/别名后点切换" autocomplete="off" style="flex:1" onkeydown="if(event.key==='Enter')selectCustomModel()">
|
|
582
|
+
<button class="action-sheet-cancel" style="background:var(--blue2);color:#fff;font-weight:600;max-width:76px;margin:0" onclick="selectCustomModel()">切换</button>
|
|
583
|
+
</div>
|
|
584
|
+
</div>
|
|
559
585
|
<button class="action-sheet-cancel" onclick="closeModelSheet()">取消</button>
|
|
560
586
|
</div>
|
|
561
587
|
</div>
|
|
@@ -696,6 +722,33 @@
|
|
|
696
722
|
</div>
|
|
697
723
|
</div>
|
|
698
724
|
|
|
725
|
+
<!-- 灵感收件箱(草稿队列):会话正忙时先记下想法,干完再发。每条强制归属一个任务 -->
|
|
726
|
+
<div id="inbox-sheet" class="action-sheet">
|
|
727
|
+
<div class="action-sheet-backdrop" onclick="closeInboxSheet()"></div>
|
|
728
|
+
<div class="action-sheet-box" style="max-height:85vh;display:flex;flex-direction:column">
|
|
729
|
+
<div class="action-sheet-title">📥 灵感收件箱 · 草稿</div>
|
|
730
|
+
<div style="font-size:0.68rem;color:var(--muted);padding:0 0.2rem 0.5rem;line-height:1.55">
|
|
731
|
+
AI 正忙时冒出的新想法,先<b>存成草稿</b>(可带图片/文件),等这轮干完再取回发送。
|
|
732
|
+
每条草稿都<b>记着属于哪个任务</b>,过几天也认得出。草稿只存在本机,没有发给模型。
|
|
733
|
+
<br>勾「<b>自动接力</b>」的草稿会在本轮干完、会话空闲后<b>自动发出</b>(你人不在也跑,按记录先后排队);
|
|
734
|
+
<span style="color:var(--yellow)">默认关</span>,不勾就只能手动取回。被中断或出错收尾的轮次不接力。
|
|
735
|
+
</div>
|
|
736
|
+
<div style="display:flex;gap:0.4rem;padding:0.3rem 0.2rem;border-top:1px solid var(--surface2)">
|
|
737
|
+
<button class="action-sheet-cancel" style="flex:1;background:var(--yellow);color:#000;font-weight:600;margin:0"
|
|
738
|
+
onclick="saveDraftFromInput('manual')">把输入框存为草稿</button>
|
|
739
|
+
</div>
|
|
740
|
+
<div style="font-size:0.66rem;color:var(--muted);padding:0.5rem 0.2rem 0.2rem;border-top:1px solid var(--surface2)">
|
|
741
|
+
<b>本任务</b>(<span id="inbox-this-title"></span>)
|
|
742
|
+
</div>
|
|
743
|
+
<div id="inbox-this" style="overflow-y:auto;padding:0.2rem 0;font-size:0.8rem;scrollbar-width:thin;min-height:3rem;max-height:34vh"></div>
|
|
744
|
+
<div style="font-size:0.66rem;color:var(--muted);padding:0.4rem 0.2rem 0.2rem;border-top:1px solid var(--surface2)">
|
|
745
|
+
<a onclick="toggleInboxOthers()" style="color:var(--blue2);cursor:pointer">▸ 其它任务的草稿 <span id="inbox-others-count"></span></a>
|
|
746
|
+
</div>
|
|
747
|
+
<div id="inbox-others" style="display:none;overflow-y:auto;padding:0.2rem 0;font-size:0.8rem;scrollbar-width:thin;max-height:26vh"></div>
|
|
748
|
+
<button class="action-sheet-cancel" onclick="closeInboxSheet()">关闭</button>
|
|
749
|
+
</div>
|
|
750
|
+
</div>
|
|
751
|
+
|
|
699
752
|
<!-- 自动跟进(会话级):长任务完成后自动汇报进度,免用户反复问 -->
|
|
700
753
|
<div id="followup-sheet" class="action-sheet">
|
|
701
754
|
<div class="action-sheet-backdrop" onclick="closeFollowUpSheet()"></div>
|
|
@@ -909,14 +962,17 @@
|
|
|
909
962
|
} else {
|
|
910
963
|
html += `<div ${C}><div ${H}>5 小时配额窗口</div><div ${T}>发送一条 Agent 消息后可查看</div></div>`;
|
|
911
964
|
}
|
|
912
|
-
|
|
965
|
+
{
|
|
966
|
+
// 只显示【当前会话】的用量(按本页 SESSION_ID 过滤),而不是列出所有会话
|
|
967
|
+
const s = (data.activeSessions || []).find(x => x.id === SESSION_ID);
|
|
913
968
|
html += `<div ${C}><div ${H}>当前会话</div>`;
|
|
914
|
-
|
|
915
|
-
if (!s.usage) continue;
|
|
969
|
+
if (s && s.usage) {
|
|
916
970
|
const u = s.usage;
|
|
917
971
|
html += `<div ${T}><b style="color:var(--text)">${s.title}</b> (${s.owner || '管理员'})</div>`;
|
|
918
972
|
html += `<div style="font-size:0.78rem;color:var(--muted);margin:0.2rem 0 0.3rem;line-height:1.5">查询 <b style="color:var(--text3)">${u.queryCount}</b>次 输入 <b style="color:var(--text3)">${((u.totalInputTokens+u.totalCacheReadTokens)/1000).toFixed(0)}K</b> 输出 <b style="color:var(--text3)">${(u.totalOutputTokens/1000).toFixed(0)}K</b> 费用 <b style="color:var(--text3)">$${u.totalCostUSD.toFixed(4)}</b></div>`;
|
|
919
973
|
if (u.modelUsage) { for (const [m, mu] of Object.entries(u.modelUsage)) { html += `<div style="font-size:0.72rem;color:var(--dim);line-height:1.4"> ${m.replace(/claude-/,'').replace(/-\d{8}$/,'').replace(/\[.*\]/,'')} ${(mu.inputTokens/1000).toFixed(0)}K/${(mu.outputTokens/1000).toFixed(0)}K $${mu.costUSD.toFixed(4)}</div>`; } }
|
|
974
|
+
} else {
|
|
975
|
+
html += `<div ${T} style="color:var(--muted)">本会话暂无用量(发送一条消息后统计)</div>`;
|
|
920
976
|
}
|
|
921
977
|
html += '</div>';
|
|
922
978
|
}
|
|
@@ -1270,6 +1326,7 @@
|
|
|
1270
1326
|
// ── 消息渲染 ──────────────────────────────────
|
|
1271
1327
|
function addInputMessage(text) {
|
|
1272
1328
|
endStream(); endToolGroup();
|
|
1329
|
+
_userScrolledUp = false; hideNewMsgHint(); // 用户主动发消息 → 回到底部看回复
|
|
1273
1330
|
_streamedTextThisCall = false;
|
|
1274
1331
|
_textRenderedThisTurn = false;
|
|
1275
1332
|
const msg = document.createElement('div');
|
|
@@ -1788,6 +1845,11 @@
|
|
|
1788
1845
|
const busy = state !== 'idle';
|
|
1789
1846
|
cancelBtn.classList.toggle('show', busy);
|
|
1790
1847
|
sendBtn.style.display = busy ? 'none' : '';
|
|
1848
|
+
// busy 时发送键让位给「存草稿」键;回到 idle 再提示有没有待发草稿
|
|
1849
|
+
const draftBtn = document.getElementById('draft-btn');
|
|
1850
|
+
if (draftBtn) draftBtn.style.display = busy ? '' : 'none';
|
|
1851
|
+
// 用 try 包住:这两个绑定声明在文件后段,初始化早期调用会撞 TDZ
|
|
1852
|
+
try { if (!busy) _draftTipDismissed = false; renderDraftTip(); } catch {}
|
|
1791
1853
|
}
|
|
1792
1854
|
_postStatus(state);
|
|
1793
1855
|
}
|
|
@@ -2230,8 +2292,9 @@
|
|
|
2230
2292
|
if (chatArea.scrollTop < chatArea.scrollHeight - chatArea.clientHeight - 100) {
|
|
2231
2293
|
_userScrolledUp = true;
|
|
2232
2294
|
} else if (chatArea.scrollTop > chatArea.scrollHeight - chatArea.clientHeight - 20) {
|
|
2233
|
-
// 回到接近底部 → 恢复 trim
|
|
2295
|
+
// 回到接近底部 → 恢复 trim + 隐藏新消息提示
|
|
2234
2296
|
_userScrolledUp = false;
|
|
2297
|
+
hideNewMsgHint();
|
|
2235
2298
|
}
|
|
2236
2299
|
// 接近顶部 100px → 拉更老历史
|
|
2237
2300
|
if (chatArea.scrollTop < 100 && _historyHasMore && !_historyLoading) {
|
|
@@ -2269,7 +2332,10 @@
|
|
|
2269
2332
|
function setPendingImage(path, blobUrl) {
|
|
2270
2333
|
_pendingImage = { path, blobUrl };
|
|
2271
2334
|
const preview = document.getElementById('img-preview');
|
|
2272
|
-
document.getElementById('img-preview-thumb')
|
|
2335
|
+
const thumb = document.getElementById('img-preview-thumb');
|
|
2336
|
+
// 从草稿取回时只剩服务器路径、没有本地 blob 预览图 —— 藏掉缩略图免得显示破图
|
|
2337
|
+
thumb.src = blobUrl || '';
|
|
2338
|
+
thumb.style.display = blobUrl ? '' : 'none';
|
|
2273
2339
|
preview.classList.add('show');
|
|
2274
2340
|
cmdInput.placeholder = '输入文字描述后发送,或直接发送分析图片...';
|
|
2275
2341
|
cmdInput.focus();
|
|
@@ -2314,6 +2380,13 @@
|
|
|
2314
2380
|
const text = cmdInput.value.trim();
|
|
2315
2381
|
if (!text && !_pendingImage && !_pendingFile) return;
|
|
2316
2382
|
|
|
2383
|
+
// AI 还在跑这一轮(可能正开着子代理,整轮 result 未到,服务端 isBusy 会拒收):
|
|
2384
|
+
// 不再把输入连同刚上传的图片一起丢掉,转存草稿,本轮干完再取回。
|
|
2385
|
+
if (currentSession?.mode === 'agent' && (workState !== 'idle' || currentSession?.busy)) {
|
|
2386
|
+
saveDraftFromInput('busy');
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2317
2390
|
if (!isController) {
|
|
2318
2391
|
if (canTakeControl()) { socket.emit('take-control', { sessionId: SESSION_ID }); }
|
|
2319
2392
|
else { addStatusMessage('其他用户控制中,无法发送'); return; }
|
|
@@ -2345,6 +2418,13 @@
|
|
|
2345
2418
|
: text;
|
|
2346
2419
|
socket.emit('input', input + '\r');
|
|
2347
2420
|
}
|
|
2421
|
+
// 这条是从草稿取回的 → 真的发出去了才销毁草稿
|
|
2422
|
+
if (_usingDraftId) {
|
|
2423
|
+
const did = _usingDraftId;
|
|
2424
|
+
_usingDraftId = null;
|
|
2425
|
+
fetch('/api/inbox/' + did, { method: 'DELETE' }).then(() => refreshInbox()).catch(() => {});
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2348
2428
|
cmdHistory.unshift(imgPath ? `[图片] ${userText}` : filePath ? `[文件] ${fileName} ${userText}` : text);
|
|
2349
2429
|
if (cmdHistory.length > 100) cmdHistory.pop();
|
|
2350
2430
|
historyIdx = -1;
|
|
@@ -2909,7 +2989,14 @@
|
|
|
2909
2989
|
document.body.appendChild(overlay);
|
|
2910
2990
|
};
|
|
2911
2991
|
|
|
2912
|
-
function scrollToBottom() {
|
|
2992
|
+
function scrollToBottom() {
|
|
2993
|
+
// 用户正往上翻看历史时不要打断(把他拽回底部);只弹「↓ 新消息」提示,回到底部再恢复自动滚
|
|
2994
|
+
if (_userScrolledUp) { showNewMsgHint(); return; }
|
|
2995
|
+
requestAnimationFrame(() => { chatArea.scrollTop = chatArea.scrollHeight; });
|
|
2996
|
+
}
|
|
2997
|
+
function showNewMsgHint() { const b = document.getElementById('new-msg-btn'); if (b) b.classList.add('show'); }
|
|
2998
|
+
function hideNewMsgHint() { const b = document.getElementById('new-msg-btn'); if (b) b.classList.remove('show'); }
|
|
2999
|
+
window.jumpToBottom = function() { _userScrolledUp = false; hideNewMsgHint(); requestAnimationFrame(() => { chatArea.scrollTop = chatArea.scrollHeight; }); };
|
|
2913
3000
|
function trimMessages() {
|
|
2914
3001
|
if (_userScrolledUp) return; // 用户在往上翻历史,禁用自动删
|
|
2915
3002
|
const msgs = chatArea.querySelectorAll('.msg');
|
|
@@ -2925,15 +3012,14 @@
|
|
|
2925
3012
|
window.closeQR = function() { document.getElementById('qr-modal').classList.remove('open'); };
|
|
2926
3013
|
|
|
2927
3014
|
// ── 模型选择 ──────────────────────────────────
|
|
3015
|
+
// 用【别名】而非硬编码全名:/model sonnet 永远指向该家族最新版,claude 升级不用改这里。
|
|
3016
|
+
// 精确版本/冷门模型用面板底部「自定义模型」输入框指定全名(如 claude-sonnet-4-6)。
|
|
2928
3017
|
const MODELS = [
|
|
2929
|
-
|
|
2930
|
-
{ id: '
|
|
2931
|
-
{ id: '
|
|
2932
|
-
{ id: '
|
|
2933
|
-
{ id: '
|
|
2934
|
-
// 备选(上一代,仍可用)
|
|
2935
|
-
{ id: 'claude-opus-4-6', label: 'Opus 4.6', desc: '上一代 Opus(备选)' },
|
|
2936
|
-
{ id: 'claude-sonnet-4-5', label: 'Sonnet 4.5', desc: '上一代 Sonnet(备选)' },
|
|
3018
|
+
{ id: 'default', label: '默认', desc: '恢复 Claude 默认模型(跟随账户/订阅设置)' },
|
|
3019
|
+
{ id: 'sonnet', label: 'Sonnet', desc: '速度+智能平衡 · 跟随最新版' },
|
|
3020
|
+
{ id: 'opus', label: 'Opus', desc: '最强推理 · agentic coding · 跟随最新版' },
|
|
3021
|
+
{ id: 'haiku', label: 'Haiku', desc: '最快最便宜 · 跟随最新版' },
|
|
3022
|
+
{ id: 'fable', label: 'Fable', desc: 'Claude 5 家族(消耗较快)· 跟随最新版' },
|
|
2937
3023
|
];
|
|
2938
3024
|
let currentModelId = '';
|
|
2939
3025
|
|
|
@@ -3149,6 +3235,12 @@
|
|
|
3149
3235
|
currentModelId = m.id;
|
|
3150
3236
|
addStatusMessage('模型已切换为 ' + m.label);
|
|
3151
3237
|
}
|
|
3238
|
+
window.selectCustomModel = function() {
|
|
3239
|
+
const v = (document.getElementById('model-custom').value || '').trim();
|
|
3240
|
+
if (!v) { addStatusMessage('请输入模型名或别名'); return; }
|
|
3241
|
+
document.getElementById('model-custom').value = '';
|
|
3242
|
+
selectModel({ id: v, label: v });
|
|
3243
|
+
};
|
|
3152
3244
|
document.getElementById('qr-modal').addEventListener('click', (e) => { if (e.target === e.currentTarget) window.closeQR(); });
|
|
3153
3245
|
|
|
3154
3246
|
// ── 恢复会话 ──────────────────────────────────
|
|
@@ -3335,6 +3427,207 @@
|
|
|
3335
3427
|
} catch {}
|
|
3336
3428
|
};
|
|
3337
3429
|
|
|
3430
|
+
// ── 灵感收件箱(草稿队列)───────────────────────────────────
|
|
3431
|
+
// AI 正忙(可能还开着子代理,整轮 result 未到)时冒出的新想法:旧行为是直接
|
|
3432
|
+
// 报错「正在处理中」,而输入框和图片预览此刻已被清空 —— 内容连同刚上传的图片一起丢。
|
|
3433
|
+
// 现在转存草稿,带归属任务,干完再取回。
|
|
3434
|
+
let _inboxCache = { drafts: [], counts: {} };
|
|
3435
|
+
let _usingDraftId = null; // 取回到输入框、等发送成功后删掉的那条
|
|
3436
|
+
let _draftTipDismissed = false;
|
|
3437
|
+
|
|
3438
|
+
async function refreshInbox(silent = true) {
|
|
3439
|
+
try {
|
|
3440
|
+
const r = await fetch('/api/inbox');
|
|
3441
|
+
const d = await r.json();
|
|
3442
|
+
_inboxCache = { drafts: d.drafts || [], counts: d.counts || {} };
|
|
3443
|
+
} catch { if (!silent) addStatusMessage('读取草稿失败'); return; }
|
|
3444
|
+
updateInboxBadge();
|
|
3445
|
+
renderDraftTip();
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
function myDrafts() { return _inboxCache.drafts.filter(d => d.sessionId === SESSION_ID); }
|
|
3449
|
+
function otherDrafts() { return _inboxCache.drafts.filter(d => d.sessionId !== SESSION_ID); }
|
|
3450
|
+
|
|
3451
|
+
function updateInboxBadge() {
|
|
3452
|
+
const badge = document.getElementById('inbox-badge');
|
|
3453
|
+
if (!badge) return;
|
|
3454
|
+
const n = myDrafts().length;
|
|
3455
|
+
badge.textContent = n || '';
|
|
3456
|
+
badge.style.display = n ? '' : 'none';
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
// 只在本轮真的干完(idle)时提示,避免跑着活还催你发下一条
|
|
3460
|
+
function renderDraftTip() {
|
|
3461
|
+
const tip = document.getElementById('draft-tip');
|
|
3462
|
+
if (!tip) return;
|
|
3463
|
+
const n = myDrafts().length;
|
|
3464
|
+
const idle = workState === 'idle' && !currentSession?.busy;
|
|
3465
|
+
if (!n || !idle || _draftTipDismissed) { tip.style.display = 'none'; return; }
|
|
3466
|
+
document.getElementById('draft-tip-text').textContent =
|
|
3467
|
+
`本任务有 ${n} 条待发草稿 —— 点这里取回`;
|
|
3468
|
+
tip.style.display = 'flex';
|
|
3469
|
+
}
|
|
3470
|
+
window.hideDraftTip = function() { _draftTipDismissed = true; renderDraftTip(); };
|
|
3471
|
+
|
|
3472
|
+
window.openInboxSheet = async function() {
|
|
3473
|
+
document.getElementById('inbox-sheet').classList.add('open');
|
|
3474
|
+
await refreshInbox(false);
|
|
3475
|
+
renderInbox();
|
|
3476
|
+
};
|
|
3477
|
+
window.closeInboxSheet = function() {
|
|
3478
|
+
document.getElementById('inbox-sheet').classList.remove('open');
|
|
3479
|
+
};
|
|
3480
|
+
window.toggleInboxOthers = function() {
|
|
3481
|
+
const box = document.getElementById('inbox-others');
|
|
3482
|
+
box.style.display = box.style.display === 'none' ? '' : 'none';
|
|
3483
|
+
};
|
|
3484
|
+
|
|
3485
|
+
function draftPreview(d) {
|
|
3486
|
+
const t = (d.text || '').replace(/\s+/g, ' ').trim();
|
|
3487
|
+
const att = [d.images?.length ? `📷${d.images.length}` : '', d.files?.length ? `📎${d.files.length}` : '']
|
|
3488
|
+
.filter(Boolean).join(' ');
|
|
3489
|
+
return (att ? att + ' ' : '') + (t || '(仅附件)');
|
|
3490
|
+
}
|
|
3491
|
+
function draftAge(ts) {
|
|
3492
|
+
const s = Math.floor((Date.now() - ts) / 1000);
|
|
3493
|
+
if (s < 60) return '刚刚';
|
|
3494
|
+
if (s < 3600) return Math.floor(s / 60) + ' 分钟前';
|
|
3495
|
+
if (s < 86400) return Math.floor(s / 3600) + ' 小时前';
|
|
3496
|
+
return Math.floor(s / 86400) + ' 天前';
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
function renderInbox() {
|
|
3500
|
+
const esc = (s) => String(s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
3501
|
+
const titleEl = document.getElementById('inbox-this-title');
|
|
3502
|
+
if (titleEl) titleEl.textContent = currentSession?.title || '本会话';
|
|
3503
|
+
|
|
3504
|
+
const mine = myDrafts();
|
|
3505
|
+
document.getElementById('inbox-this').innerHTML = mine.length ? mine.map(d => `
|
|
3506
|
+
<div style="display:flex;gap:0.5rem;align-items:flex-start;padding:0.45rem 0.2rem;border-bottom:1px solid var(--surface2)">
|
|
3507
|
+
<div style="flex:1;min-width:0">
|
|
3508
|
+
<div style="color:var(--text);line-height:1.45;word-break:break-word">${esc(draftPreview(d))}</div>
|
|
3509
|
+
<div style="font-size:0.66rem;color:var(--muted);margin-top:0.15rem">
|
|
3510
|
+
${draftAge(d.createdAt)}${d.source === 'busy' ? ' · 忙时自动存' : ''}
|
|
3511
|
+
<label style="margin-left:0.5rem;cursor:pointer;color:${d.autoSend ? 'var(--yellow)' : 'var(--muted)'}"
|
|
3512
|
+
title="勾上后:本轮干完、会话空闲时自动发出这条(不用你在页面前)。默认关。">
|
|
3513
|
+
<input type="checkbox" ${d.autoSend ? 'checked' : ''} onchange="toggleAutoSend('${d.id}', this.checked)"
|
|
3514
|
+
style="vertical-align:-1px"> 自动接力
|
|
3515
|
+
</label>
|
|
3516
|
+
</div>
|
|
3517
|
+
</div>
|
|
3518
|
+
<button class="action-sheet-cancel" style="margin:0;padding:0.25rem 0.5rem;font-size:0.72rem;background:var(--green);color:#fff" onclick="useDraft('${d.id}')">取回</button>
|
|
3519
|
+
<button class="action-sheet-cancel" style="margin:0;padding:0.25rem 0.5rem;font-size:0.72rem" onclick="deleteDraft('${d.id}')">删</button>
|
|
3520
|
+
</div>`).join('')
|
|
3521
|
+
: '<div style="color:var(--muted);font-size:0.75rem;padding:0.5rem">(本任务暂无草稿)</div>';
|
|
3522
|
+
|
|
3523
|
+
const others = otherDrafts();
|
|
3524
|
+
document.getElementById('inbox-others-count').textContent = others.length ? `(${others.length})` : '(0)';
|
|
3525
|
+
document.getElementById('inbox-others').innerHTML = others.length ? others.map(d => `
|
|
3526
|
+
<div style="display:flex;gap:0.5rem;align-items:flex-start;padding:0.45rem 0.2rem;border-bottom:1px solid var(--surface2)">
|
|
3527
|
+
<div style="flex:1;min-width:0">
|
|
3528
|
+
<div style="font-size:0.68rem;color:var(--blue2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
|
3529
|
+
${esc(d.sessionTitle || '(未知任务)')}${d.projectName ? ' · ' + esc(d.projectName) : ''}${d.sessionAlive ? '' : ' <span style="color:var(--muted)">(已结束)</span>'}
|
|
3530
|
+
</div>
|
|
3531
|
+
<div style="color:var(--text2);line-height:1.4;word-break:break-word">${esc(draftPreview(d))}</div>
|
|
3532
|
+
<div style="font-size:0.66rem;color:var(--muted);margin-top:0.15rem">${draftAge(d.createdAt)}</div>
|
|
3533
|
+
</div>
|
|
3534
|
+
<button class="action-sheet-cancel" style="margin:0;padding:0.25rem 0.5rem;font-size:0.72rem" onclick="reassignDraft('${d.id}')" title="改投到本任务">改投</button>
|
|
3535
|
+
<button class="action-sheet-cancel" style="margin:0;padding:0.25rem 0.5rem;font-size:0.72rem" onclick="deleteDraft('${d.id}')">删</button>
|
|
3536
|
+
</div>`).join('')
|
|
3537
|
+
: '<div style="color:var(--muted);font-size:0.75rem;padding:0.5rem">(没有其它任务的草稿)</div>';
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
// 把当前输入框(文字 + 已上传的图片/文件)整体存成草稿
|
|
3541
|
+
window.saveDraftFromInput = async function(source = 'manual') {
|
|
3542
|
+
const text = cmdInput.value.trim();
|
|
3543
|
+
if (!text && !_pendingImage && !_pendingFile) { addStatusMessage('输入框是空的,没什么可存'); return false; }
|
|
3544
|
+
const body = {
|
|
3545
|
+
text,
|
|
3546
|
+
sessionId: SESSION_ID,
|
|
3547
|
+
source,
|
|
3548
|
+
images: _pendingImage ? [{ path: _pendingImage.path, name: '图片' }] : [],
|
|
3549
|
+
files: _pendingFile ? [{ path: _pendingFile.path, name: _pendingFile.name }] : [],
|
|
3550
|
+
};
|
|
3551
|
+
try {
|
|
3552
|
+
const r = await fetch('/api/inbox', {
|
|
3553
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
3554
|
+
});
|
|
3555
|
+
const d = await r.json();
|
|
3556
|
+
if (d.error) { addStatusMessage('存草稿失败: ' + d.error); return false; }
|
|
3557
|
+
cmdInput.value = '';
|
|
3558
|
+
cmdInput.style.height = 'auto';
|
|
3559
|
+
clearPendingImage();
|
|
3560
|
+
clearPendingFile();
|
|
3561
|
+
_usingDraftId = null;
|
|
3562
|
+
_draftTipDismissed = false;
|
|
3563
|
+
await refreshInbox();
|
|
3564
|
+
if (document.getElementById('inbox-sheet').classList.contains('open')) renderInbox();
|
|
3565
|
+
addStatusMessage(source === 'busy'
|
|
3566
|
+
? '📥 AI 还在跑,已把这条存进草稿(没有丢),干完后可取回发送'
|
|
3567
|
+
: '📥 已存进草稿');
|
|
3568
|
+
return true;
|
|
3569
|
+
} catch (e) {
|
|
3570
|
+
addStatusMessage('存草稿失败: ' + e.message);
|
|
3571
|
+
return false;
|
|
3572
|
+
}
|
|
3573
|
+
};
|
|
3574
|
+
|
|
3575
|
+
// 取回:填回输入框(可以改完再发),草稿先留着,发送成功才删
|
|
3576
|
+
window.useDraft = function(id) {
|
|
3577
|
+
const d = _inboxCache.drafts.find(x => x.id === id);
|
|
3578
|
+
if (!d) return;
|
|
3579
|
+
cmdInput.value = d.text || '';
|
|
3580
|
+
cmdInput.style.height = 'auto';
|
|
3581
|
+
cmdInput.style.height = Math.min(cmdInput.scrollHeight, 200) + 'px';
|
|
3582
|
+
clearPendingImage();
|
|
3583
|
+
clearPendingFile();
|
|
3584
|
+
if (d.images?.length) setPendingImage(d.images[0].path, '');
|
|
3585
|
+
if (d.files?.length) {
|
|
3586
|
+
_pendingFile = { path: d.files[0].path, name: d.files[0].name };
|
|
3587
|
+
const fp = document.getElementById('file-preview');
|
|
3588
|
+
const nameEl = document.getElementById('file-preview-name');
|
|
3589
|
+
if (nameEl) nameEl.textContent = _pendingFile.name;
|
|
3590
|
+
if (fp) fp.style.display = 'flex';
|
|
3591
|
+
}
|
|
3592
|
+
_usingDraftId = id;
|
|
3593
|
+
closeInboxSheet();
|
|
3594
|
+
cmdInput.focus();
|
|
3595
|
+
};
|
|
3596
|
+
|
|
3597
|
+
window.deleteDraft = async function(id) {
|
|
3598
|
+
try { await fetch('/api/inbox/' + id, { method: 'DELETE' }); } catch {}
|
|
3599
|
+
if (_usingDraftId === id) _usingDraftId = null;
|
|
3600
|
+
await refreshInbox();
|
|
3601
|
+
renderInbox();
|
|
3602
|
+
};
|
|
3603
|
+
|
|
3604
|
+
// 自动接力开关(逐条控制,默认关):勾上后本轮干完、会话空闲 3 秒仍空闲就自动发出。
|
|
3605
|
+
// 判定在服务端做(agent.js),所以人不在页面前也照跑;被中断/出错收尾的轮次不接力。
|
|
3606
|
+
window.toggleAutoSend = async function(id, on) {
|
|
3607
|
+
try {
|
|
3608
|
+
await fetch('/api/inbox/' + id, {
|
|
3609
|
+
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
3610
|
+
body: JSON.stringify({ autoSend: !!on }),
|
|
3611
|
+
});
|
|
3612
|
+
} catch {}
|
|
3613
|
+
await refreshInbox();
|
|
3614
|
+
renderInbox();
|
|
3615
|
+
addStatusMessage(on ? '📥 这条已设为「自动接力」:本轮干完会自动发出' : '📥 已取消自动接力');
|
|
3616
|
+
};
|
|
3617
|
+
|
|
3618
|
+
// 改投:会话结束了 / 当初记错任务了,把草稿挪到当前会话名下
|
|
3619
|
+
window.reassignDraft = async function(id) {
|
|
3620
|
+
try {
|
|
3621
|
+
await fetch('/api/inbox/' + id, {
|
|
3622
|
+
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
3623
|
+
body: JSON.stringify({ sessionId: SESSION_ID }),
|
|
3624
|
+
});
|
|
3625
|
+
} catch {}
|
|
3626
|
+
await refreshInbox();
|
|
3627
|
+
renderInbox();
|
|
3628
|
+
addStatusMessage('📥 已改投到本任务');
|
|
3629
|
+
};
|
|
3630
|
+
|
|
3338
3631
|
// ── 自动跟进(会话级):长任务完成后自动汇报进度 ──────────────
|
|
3339
3632
|
window.openFollowUpSheet = function() {
|
|
3340
3633
|
document.getElementById('followup-sheet').classList.add('open');
|
|
@@ -3702,7 +3995,14 @@
|
|
|
3702
3995
|
runSkill(name);
|
|
3703
3996
|
};
|
|
3704
3997
|
// 加入会话后加载 skill
|
|
3705
|
-
socket.on('joined', () => {
|
|
3998
|
+
socket.on('joined', () => {
|
|
3999
|
+
if (currentSession?.mode === 'agent') { loadSkills(); refreshInbox(); }
|
|
4000
|
+
});
|
|
4001
|
+
// 服务端自动接力发出了一条草稿 → 同步角标/列表
|
|
4002
|
+
socket.on('draft-relayed', () => {
|
|
4003
|
+
refreshInbox();
|
|
4004
|
+
if (document.getElementById('inbox-sheet')?.classList.contains('open')) renderInbox();
|
|
4005
|
+
});
|
|
3706
4006
|
|
|
3707
4007
|
// ── 技能管理 ──────────────────────────────────
|
|
3708
4008
|
let _editingSkill = null; // 编辑中的技能名,null 表示新建
|