@wendongfly/myhi 1.3.96 → 1.3.97
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 +240 -233
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -1581,7 +1581,6 @@
|
|
|
1581
1581
|
break;
|
|
1582
1582
|
case 'result':
|
|
1583
1583
|
removeThinking();
|
|
1584
|
-
setWorkState('idle');
|
|
1585
1584
|
// msg.result 是整轮的最终答复文本,纯文字回答时与本轮已渲染的文本重复;
|
|
1586
1585
|
// 本轮已经通过流式或 assistant 事件渲染过就跳过,避免生成第二个独立气泡
|
|
1587
1586
|
if (msg.result && !_textRenderedThisTurn) {
|
|
@@ -1591,6 +1590,9 @@
|
|
|
1591
1590
|
endStream();
|
|
1592
1591
|
const cost = msg.total_cost_usd ? ` ($${msg.total_cost_usd.toFixed(4)})` : '';
|
|
1593
1592
|
addStatusMessage(`完成${cost}`);
|
|
1593
|
+
// 关键:idle 放最后——渲染最终答复的 addAssistantMessage 会把状态置回 'working',
|
|
1594
|
+
// 若在其之前置 idle 会被顶回「执行中」+ 取消按钮不复位。渲染全部完成后再回 idle。
|
|
1595
|
+
setWorkState('idle');
|
|
1594
1596
|
break;
|
|
1595
1597
|
case 'content_block_start':
|
|
1596
1598
|
// 流式内容块开始:thinking 时创建占位,由 thinking_delta 增量填充;
|
|
@@ -1716,6 +1718,11 @@
|
|
|
1716
1718
|
_textRenderedThisTurn = true;
|
|
1717
1719
|
}
|
|
1718
1720
|
|
|
1721
|
+
// 回放是历史快照:结尾按会话真实忙闲复位状态。否则回放里最后一个 addToolMessage
|
|
1722
|
+
// 把状态留在 'working',会造成「已完成的会话重进/刷新后仍显示执行中、发送键停在红色取消方框」。
|
|
1723
|
+
// 中途 socket 重连(有 inflight 流式)或会话确实在忙 → working;否则 idle。
|
|
1724
|
+
setWorkState((inflightText || currentSession?.busy) ? 'working' : 'idle');
|
|
1725
|
+
|
|
1719
1726
|
scrollToBottom();
|
|
1720
1727
|
});
|
|
1721
1728
|
|
|
@@ -2320,89 +2327,89 @@
|
|
|
2320
2327
|
if (diff < 172800000) return '昨天';
|
|
2321
2328
|
return (d.getMonth() + 1) + '月' + d.getDate() + '日';
|
|
2322
2329
|
}
|
|
2323
|
-
function switchSessTime(s) {
|
|
2324
|
-
return (s.lastActivityAt || (s.createdAt ? new Date(s.createdAt).getTime() : 0)) || 0;
|
|
2325
|
-
}
|
|
2326
|
-
window.openSwitchSheet = async function() {
|
|
2327
|
-
let sessions = [];
|
|
2328
|
-
try {
|
|
2329
|
-
const res = await fetch('/api/sessions', { headers: { 'Cache-Control': 'no-cache' } });
|
|
2330
|
-
sessions = await res.json();
|
|
2331
|
-
} catch { addStatusMessage && addStatusMessage('会话列表加载失败'); return; }
|
|
2332
|
-
sessions.sort((a, b) => switchSessTime(b) - switchSessTime(a)); // 最新在前
|
|
2333
|
-
const esc2 = (s) => { const d = document.createElement('div'); d.textContent = s == null ? '' : s; return d.innerHTML; };
|
|
2334
|
-
let mode = localStorage.getItem('myhi_group_mode') || 'time'; // 与首页共用记忆
|
|
2335
|
-
|
|
2336
|
-
const rowHTML = (s) => {
|
|
2337
|
-
const cur = s.id === SESSION_ID;
|
|
2338
|
-
const proj = (s.cwd || '').replace(/\\/g, '/').split('/').filter(Boolean).slice(-1)[0] || '';
|
|
2339
|
-
const tags = (s.tags || []).map(x => '<span style="font-size:0.62rem;color:#a9b6c2;background:rgba(124,58,237,0.14);border:1px solid rgba(124,58,237,0.35);border-radius:999px;padding:0 0.4rem">' + esc2(x) + '</span>').join(' ');
|
|
2340
|
-
const sub = [proj, switchRelTime(switchSessTime(s)), s.busy ? '运行中' : '', s.controlHolderName ? (s.controlHolderName + ' 控制中') : ''].filter(Boolean).join(' · ');
|
|
2341
|
-
return '<div data-sid="' + esc2(s.id) + '" style="display:flex;align-items:center;gap:0.6rem;padding:0.7rem 1.1rem;border-top:1px solid #21262d;cursor:pointer;' + (cur ? 'background:rgba(124,58,237,0.12)' : '') + '">' +
|
|
2342
|
-
'<span style="width:8px;height:8px;border-radius:50%;flex-shrink:0;background:' + (s.alive ? '#3fb950' : '#6e7681') + '"></span>' +
|
|
2343
|
-
'<div style="flex:1;min-width:0">' +
|
|
2344
|
-
'<div style="font-size:0.9rem;color:#e6edf3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc2(s.title || '(无标题)') + ' ' + (cur ? '<span style="font-size:0.68rem;color:#7c3aed">· 当前</span>' : '') + '</div>' +
|
|
2345
|
-
'<div style="font-size:0.72rem;color:#8b949e;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc2(sub) + '</div>' +
|
|
2346
|
-
(tags ? '<div style="display:flex;flex-wrap:wrap;gap:0.25rem;margin-top:0.25rem">' + tags + '</div>' : '') +
|
|
2347
|
-
'</div></div>';
|
|
2348
|
-
};
|
|
2349
|
-
const groupHdr = (label, n) => '<div style="padding:0.5rem 1.1rem 0.3rem;color:#8b949e;font-size:0.72rem;font-weight:600;background:#0d1117">' + esc2(label) + ' · <span style="font-weight:400">' + n + '</span></div>';
|
|
2350
|
-
const pushInto = (map, key, s) => { if (!map.has(key)) map.set(key, []); map.get(key).push(s); };
|
|
2351
|
-
|
|
2352
|
-
const buildBody = () => {
|
|
2353
|
-
if (!sessions.length) return '<div style="padding:1rem;color:#8b949e;text-align:center">暂无其他会话</div>';
|
|
2354
|
-
if (mode === 'project') {
|
|
2355
|
-
const g = new Map();
|
|
2356
|
-
for (const s of sessions) pushInto(g, (s.cwd || '').replace(/\\/g, '/').split('/').filter(Boolean).slice(-1)[0] || '(默认目录)', s);
|
|
2357
|
-
return [...g.entries()].sort((a, b) => switchSessTime(b[1][0]) - switchSessTime(a[1][0]))
|
|
2358
|
-
.map(([name, arr]) => groupHdr('📁 ' + name, arr.length) + arr.map(rowHTML).join('')).join('');
|
|
2359
|
-
}
|
|
2360
|
-
if (mode === 'tag') {
|
|
2361
|
-
const g = new Map(); const untag = [];
|
|
2362
|
-
for (const s of sessions) { const ts = s.tags || []; if (!ts.length) { untag.push(s); continue; } for (const t of ts) pushInto(g, t, s); }
|
|
2363
|
-
let h = [...g.entries()].sort((a, b) => switchSessTime(b[1][0]) - switchSessTime(a[1][0]))
|
|
2364
|
-
.map(([name, arr]) => groupHdr('🏷 ' + name, arr.length) + arr.map(rowHTML).join('')).join('');
|
|
2365
|
-
if (untag.length) h += groupHdr('未分类', untag.length) + untag.map(rowHTML).join('');
|
|
2366
|
-
return h;
|
|
2367
|
-
}
|
|
2368
|
-
return sessions.map(rowHTML).join('');
|
|
2369
|
-
};
|
|
2370
|
-
|
|
2371
|
-
const overlay = document.createElement('div');
|
|
2372
|
-
overlay.style.cssText = 'position:fixed;inset:0;z-index:120;background:rgba(0,0,0,0.6);display:flex;align-items:flex-end;justify-content:center';
|
|
2373
|
-
const box = document.createElement('div');
|
|
2374
|
-
box.style.cssText = 'background:#161b22;border-radius:18px 18px 0 0;width:100%;max-width:640px;max-height:78vh;display:flex;flex-direction:column;padding-bottom:max(0.8rem,env(safe-area-inset-bottom))';
|
|
2375
|
-
const segBtn = (m, label) => '<button class="sw-seg" data-m="' + m + '" style="background:' + (mode === m ? '#7c3aed' : 'none') + ';color:' + (mode === m ? '#fff' : '#8b949e') + ';border:none;font-size:0.74rem;padding:0.3rem 0.7rem;cursor:pointer;' + (m !== 'time' ? 'border-left:1px solid #30363d' : '') + '">' + label + '</button>';
|
|
2376
|
-
box.innerHTML = '<div style="padding:0.75rem 1.1rem 0.5rem;display:flex;justify-content:space-between;align-items:center;gap:0.5rem">' +
|
|
2377
|
-
'<span style="font-size:0.85rem;font-weight:600;color:#e6edf3">切换会话(' + sessions.length + ')</span>' +
|
|
2378
|
-
'<div style="display:flex;align-items:center;gap:0.5rem">' +
|
|
2379
|
-
'<div style="display:inline-flex;border:1px solid #30363d;border-radius:7px;overflow:hidden">' + segBtn('time', '最近') + segBtn('project', '项目') + segBtn('tag', '标签') + '</div>' +
|
|
2380
|
-
'<button id="sw-home" style="background:none;border:1px solid #30363d;color:#8b949e;font-size:0.72rem;padding:0.25rem 0.6rem;border-radius:6px;cursor:pointer">+ 新建</button>' +
|
|
2381
|
-
'</div>' +
|
|
2382
|
-
'</div>' +
|
|
2383
|
-
'<div id="sw-body" style="overflow:auto">' + buildBody() + '</div>' +
|
|
2384
|
-
'<button id="sw-cancel" style="margin:0.5rem 1.1rem 0;background:none;border:1px solid #30363d;color:#8b949e;font-size:0.85rem;padding:0.7rem;border-radius:10px;cursor:pointer">取消</button>';
|
|
2385
|
-
overlay.appendChild(box);
|
|
2386
|
-
const close = () => overlay.remove();
|
|
2387
|
-
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
|
2388
|
-
box.querySelector('#sw-cancel').onclick = close;
|
|
2389
|
-
box.querySelector('#sw-home').onclick = () => { window.location.href = '/'; };
|
|
2390
|
-
const bindRows = () => box.querySelectorAll('[data-sid]').forEach(row => row.onclick = () => {
|
|
2391
|
-
const sid = row.dataset.sid;
|
|
2392
|
-
if (sid && sid !== SESSION_ID) window.location.href = '/terminal/' + sid;
|
|
2393
|
-
else close();
|
|
2394
|
-
});
|
|
2395
|
-
bindRows();
|
|
2396
|
-
box.querySelectorAll('.sw-seg').forEach(b => b.onclick = () => {
|
|
2397
|
-
mode = b.dataset.m;
|
|
2398
|
-
localStorage.setItem('myhi_group_mode', mode);
|
|
2399
|
-
box.querySelectorAll('.sw-seg').forEach(x => { const on = x.dataset.m === mode; x.style.background = on ? '#7c3aed' : 'none'; x.style.color = on ? '#fff' : '#8b949e'; });
|
|
2400
|
-
box.querySelector('#sw-body').innerHTML = buildBody();
|
|
2401
|
-
bindRows();
|
|
2402
|
-
});
|
|
2403
|
-
document.body.appendChild(overlay);
|
|
2404
|
-
};
|
|
2405
|
-
|
|
2330
|
+
function switchSessTime(s) {
|
|
2331
|
+
return (s.lastActivityAt || (s.createdAt ? new Date(s.createdAt).getTime() : 0)) || 0;
|
|
2332
|
+
}
|
|
2333
|
+
window.openSwitchSheet = async function() {
|
|
2334
|
+
let sessions = [];
|
|
2335
|
+
try {
|
|
2336
|
+
const res = await fetch('/api/sessions', { headers: { 'Cache-Control': 'no-cache' } });
|
|
2337
|
+
sessions = await res.json();
|
|
2338
|
+
} catch { addStatusMessage && addStatusMessage('会话列表加载失败'); return; }
|
|
2339
|
+
sessions.sort((a, b) => switchSessTime(b) - switchSessTime(a)); // 最新在前
|
|
2340
|
+
const esc2 = (s) => { const d = document.createElement('div'); d.textContent = s == null ? '' : s; return d.innerHTML; };
|
|
2341
|
+
let mode = localStorage.getItem('myhi_group_mode') || 'time'; // 与首页共用记忆
|
|
2342
|
+
|
|
2343
|
+
const rowHTML = (s) => {
|
|
2344
|
+
const cur = s.id === SESSION_ID;
|
|
2345
|
+
const proj = (s.cwd || '').replace(/\\/g, '/').split('/').filter(Boolean).slice(-1)[0] || '';
|
|
2346
|
+
const tags = (s.tags || []).map(x => '<span style="font-size:0.62rem;color:#a9b6c2;background:rgba(124,58,237,0.14);border:1px solid rgba(124,58,237,0.35);border-radius:999px;padding:0 0.4rem">' + esc2(x) + '</span>').join(' ');
|
|
2347
|
+
const sub = [proj, switchRelTime(switchSessTime(s)), s.busy ? '运行中' : '', s.controlHolderName ? (s.controlHolderName + ' 控制中') : ''].filter(Boolean).join(' · ');
|
|
2348
|
+
return '<div data-sid="' + esc2(s.id) + '" style="display:flex;align-items:center;gap:0.6rem;padding:0.7rem 1.1rem;border-top:1px solid #21262d;cursor:pointer;' + (cur ? 'background:rgba(124,58,237,0.12)' : '') + '">' +
|
|
2349
|
+
'<span style="width:8px;height:8px;border-radius:50%;flex-shrink:0;background:' + (s.alive ? '#3fb950' : '#6e7681') + '"></span>' +
|
|
2350
|
+
'<div style="flex:1;min-width:0">' +
|
|
2351
|
+
'<div style="font-size:0.9rem;color:#e6edf3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc2(s.title || '(无标题)') + ' ' + (cur ? '<span style="font-size:0.68rem;color:#7c3aed">· 当前</span>' : '') + '</div>' +
|
|
2352
|
+
'<div style="font-size:0.72rem;color:#8b949e;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc2(sub) + '</div>' +
|
|
2353
|
+
(tags ? '<div style="display:flex;flex-wrap:wrap;gap:0.25rem;margin-top:0.25rem">' + tags + '</div>' : '') +
|
|
2354
|
+
'</div></div>';
|
|
2355
|
+
};
|
|
2356
|
+
const groupHdr = (label, n) => '<div style="padding:0.5rem 1.1rem 0.3rem;color:#8b949e;font-size:0.72rem;font-weight:600;background:#0d1117">' + esc2(label) + ' · <span style="font-weight:400">' + n + '</span></div>';
|
|
2357
|
+
const pushInto = (map, key, s) => { if (!map.has(key)) map.set(key, []); map.get(key).push(s); };
|
|
2358
|
+
|
|
2359
|
+
const buildBody = () => {
|
|
2360
|
+
if (!sessions.length) return '<div style="padding:1rem;color:#8b949e;text-align:center">暂无其他会话</div>';
|
|
2361
|
+
if (mode === 'project') {
|
|
2362
|
+
const g = new Map();
|
|
2363
|
+
for (const s of sessions) pushInto(g, (s.cwd || '').replace(/\\/g, '/').split('/').filter(Boolean).slice(-1)[0] || '(默认目录)', s);
|
|
2364
|
+
return [...g.entries()].sort((a, b) => switchSessTime(b[1][0]) - switchSessTime(a[1][0]))
|
|
2365
|
+
.map(([name, arr]) => groupHdr('📁 ' + name, arr.length) + arr.map(rowHTML).join('')).join('');
|
|
2366
|
+
}
|
|
2367
|
+
if (mode === 'tag') {
|
|
2368
|
+
const g = new Map(); const untag = [];
|
|
2369
|
+
for (const s of sessions) { const ts = s.tags || []; if (!ts.length) { untag.push(s); continue; } for (const t of ts) pushInto(g, t, s); }
|
|
2370
|
+
let h = [...g.entries()].sort((a, b) => switchSessTime(b[1][0]) - switchSessTime(a[1][0]))
|
|
2371
|
+
.map(([name, arr]) => groupHdr('🏷 ' + name, arr.length) + arr.map(rowHTML).join('')).join('');
|
|
2372
|
+
if (untag.length) h += groupHdr('未分类', untag.length) + untag.map(rowHTML).join('');
|
|
2373
|
+
return h;
|
|
2374
|
+
}
|
|
2375
|
+
return sessions.map(rowHTML).join('');
|
|
2376
|
+
};
|
|
2377
|
+
|
|
2378
|
+
const overlay = document.createElement('div');
|
|
2379
|
+
overlay.style.cssText = 'position:fixed;inset:0;z-index:120;background:rgba(0,0,0,0.6);display:flex;align-items:flex-end;justify-content:center';
|
|
2380
|
+
const box = document.createElement('div');
|
|
2381
|
+
box.style.cssText = 'background:#161b22;border-radius:18px 18px 0 0;width:100%;max-width:640px;max-height:78vh;display:flex;flex-direction:column;padding-bottom:max(0.8rem,env(safe-area-inset-bottom))';
|
|
2382
|
+
const segBtn = (m, label) => '<button class="sw-seg" data-m="' + m + '" style="background:' + (mode === m ? '#7c3aed' : 'none') + ';color:' + (mode === m ? '#fff' : '#8b949e') + ';border:none;font-size:0.74rem;padding:0.3rem 0.7rem;cursor:pointer;' + (m !== 'time' ? 'border-left:1px solid #30363d' : '') + '">' + label + '</button>';
|
|
2383
|
+
box.innerHTML = '<div style="padding:0.75rem 1.1rem 0.5rem;display:flex;justify-content:space-between;align-items:center;gap:0.5rem">' +
|
|
2384
|
+
'<span style="font-size:0.85rem;font-weight:600;color:#e6edf3">切换会话(' + sessions.length + ')</span>' +
|
|
2385
|
+
'<div style="display:flex;align-items:center;gap:0.5rem">' +
|
|
2386
|
+
'<div style="display:inline-flex;border:1px solid #30363d;border-radius:7px;overflow:hidden">' + segBtn('time', '最近') + segBtn('project', '项目') + segBtn('tag', '标签') + '</div>' +
|
|
2387
|
+
'<button id="sw-home" style="background:none;border:1px solid #30363d;color:#8b949e;font-size:0.72rem;padding:0.25rem 0.6rem;border-radius:6px;cursor:pointer">+ 新建</button>' +
|
|
2388
|
+
'</div>' +
|
|
2389
|
+
'</div>' +
|
|
2390
|
+
'<div id="sw-body" style="overflow:auto">' + buildBody() + '</div>' +
|
|
2391
|
+
'<button id="sw-cancel" style="margin:0.5rem 1.1rem 0;background:none;border:1px solid #30363d;color:#8b949e;font-size:0.85rem;padding:0.7rem;border-radius:10px;cursor:pointer">取消</button>';
|
|
2392
|
+
overlay.appendChild(box);
|
|
2393
|
+
const close = () => overlay.remove();
|
|
2394
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
|
2395
|
+
box.querySelector('#sw-cancel').onclick = close;
|
|
2396
|
+
box.querySelector('#sw-home').onclick = () => { window.location.href = '/'; };
|
|
2397
|
+
const bindRows = () => box.querySelectorAll('[data-sid]').forEach(row => row.onclick = () => {
|
|
2398
|
+
const sid = row.dataset.sid;
|
|
2399
|
+
if (sid && sid !== SESSION_ID) window.location.href = '/terminal/' + sid;
|
|
2400
|
+
else close();
|
|
2401
|
+
});
|
|
2402
|
+
bindRows();
|
|
2403
|
+
box.querySelectorAll('.sw-seg').forEach(b => b.onclick = () => {
|
|
2404
|
+
mode = b.dataset.m;
|
|
2405
|
+
localStorage.setItem('myhi_group_mode', mode);
|
|
2406
|
+
box.querySelectorAll('.sw-seg').forEach(x => { const on = x.dataset.m === mode; x.style.background = on ? '#7c3aed' : 'none'; x.style.color = on ? '#fff' : '#8b949e'; });
|
|
2407
|
+
box.querySelector('#sw-body').innerHTML = buildBody();
|
|
2408
|
+
bindRows();
|
|
2409
|
+
});
|
|
2410
|
+
document.body.appendChild(overlay);
|
|
2411
|
+
};
|
|
2412
|
+
|
|
2406
2413
|
function scrollToBottom() { requestAnimationFrame(() => { chatArea.scrollTop = chatArea.scrollHeight; }); }
|
|
2407
2414
|
function trimMessages() {
|
|
2408
2415
|
if (_userScrolledUp) return; // 用户在往上翻历史,禁用自动删
|
|
@@ -2431,155 +2438,155 @@
|
|
|
2431
2438
|
];
|
|
2432
2439
|
let currentModelId = '';
|
|
2433
2440
|
|
|
2434
|
-
// ── AskUserQuestion 弹窗(累加多题)──
|
|
2435
|
-
// AI 一轮里可能连发多个 AskUserQuestion 调用;旧逻辑每次 openAskSheet 都清空覆盖,
|
|
2436
|
-
// 导致只看到最后一题、其余被顶掉无法回答。改为把本轮所有提问累加进同一弹窗,
|
|
2437
|
-
// 多题一次答完;跨重渲染保留已选答案;去重防同一题被 assistant 事件+独立 tool_use 事件各触发一次。
|
|
2438
|
-
let _askQuestions = [];
|
|
2439
|
-
let askMultiState = null; // { answers: [{question,label}|null] },下标与 _askQuestions 对齐
|
|
2440
|
-
|
|
2441
|
-
function _normalizeAsk(input) {
|
|
2442
|
-
const q = input?.questions || input?.question;
|
|
2443
|
-
const list = Array.isArray(q)
|
|
2444
|
-
? q
|
|
2445
|
-
: [{ question: (typeof q === 'string' ? q : '') || input?.question || '请选择', options: input?.options, header: input?.header }];
|
|
2446
|
-
return list.map((x) => ({
|
|
2447
|
-
question: x.question || x.text || '',
|
|
2448
|
-
options: x.options || x.choices || [],
|
|
2449
|
-
header: x.header,
|
|
2450
|
-
}));
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
|
-
window.openAskSheet = function(input) {
|
|
2454
|
-
for (const q of _normalizeAsk(input)) {
|
|
2455
|
-
if (!q.question || !_askQuestions.some((e) => e.question === q.question)) _askQuestions.push(q);
|
|
2456
|
-
}
|
|
2457
|
-
if (!_askQuestions.length) return;
|
|
2458
|
-
_renderAskSheet();
|
|
2459
|
-
};
|
|
2460
|
-
|
|
2461
|
-
function _renderAskSheet() {
|
|
2462
|
-
const sheet = document.getElementById('ask-sheet');
|
|
2463
|
-
const title = document.getElementById('ask-sheet-title');
|
|
2464
|
-
const body = document.getElementById('ask-sheet-body');
|
|
2465
|
-
const inp = document.getElementById('ask-sheet-input');
|
|
2466
|
-
const inputWrap = document.getElementById('ask-sheet-input-wrap');
|
|
2467
|
-
const btnMulti = document.getElementById('ask-sheet-submit-multi');
|
|
2468
|
-
const btnSingle = document.getElementById('ask-sheet-submit-single');
|
|
2469
|
-
|
|
2470
|
-
const qList = _askQuestions;
|
|
2471
|
-
const isMulti = qList.length > 1;
|
|
2472
|
-
if (!askMultiState) askMultiState = { answers: [] };
|
|
2473
|
-
while (askMultiState.answers.length < qList.length) askMultiState.answers.push(null);
|
|
2474
|
-
|
|
2475
|
-
title.textContent = (qList[0] && qList[0].header) || (isMulti ? '提问(' + qList.length + ' 题)' : '提问');
|
|
2476
|
-
if (isMulti) {
|
|
2477
|
-
inputWrap.style.display = 'none';
|
|
2478
|
-
btnSingle.style.display = 'none';
|
|
2479
|
-
btnMulti.style.display = '';
|
|
2480
|
-
} else {
|
|
2481
|
-
inputWrap.style.display = '';
|
|
2482
|
-
btnSingle.style.display = '';
|
|
2483
|
-
btnMulti.style.display = 'none';
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
body.innerHTML = '';
|
|
2487
|
-
qList.forEach((q, qIdx) => {
|
|
2488
|
-
const qText = q.question || '';
|
|
2489
|
-
const block = document.createElement('div');
|
|
2490
|
-
block.className = isMulti ? 'ask-q-block' : '';
|
|
2491
|
-
if (isMulti) {
|
|
2492
|
-
const label = document.createElement('div');
|
|
2493
|
-
label.className = 'ask-q-label';
|
|
2494
|
-
label.textContent = 'Q' + (qIdx + 1);
|
|
2495
|
-
block.appendChild(label);
|
|
2496
|
-
}
|
|
2497
|
-
if (qText) {
|
|
2498
|
-
const desc = document.createElement('div');
|
|
2499
|
-
if (isMulti) { desc.className = 'ask-q-text'; desc.textContent = qText; }
|
|
2500
|
-
else { desc.style.cssText = 'padding:0.5rem 0.3rem;font-size:0.82rem;color:#c9d1d9;line-height:1.5;white-space:pre-wrap'; desc.textContent = qText; }
|
|
2501
|
-
block.appendChild(desc);
|
|
2502
|
-
}
|
|
2503
|
-
if (q.options?.length) {
|
|
2504
|
-
q.options.forEach((opt) => {
|
|
2505
|
-
const item = document.createElement('div');
|
|
2506
|
-
item.className = 'action-sheet-item';
|
|
2507
|
-
const label = opt.label || opt.value || String(opt);
|
|
2508
|
-
item.innerHTML = '<div><div>' + escHtml(label) + '</div>' + (opt.description ? '<div class="desc">' + escHtml(opt.description) + '</div>' : '') + '</div><span class="check" style="display:none">✓</span>';
|
|
2509
|
-
if (isMulti && askMultiState.answers[qIdx]?.label === label) {
|
|
2510
|
-
item.classList.add('ask-opt-selected');
|
|
2511
|
-
const c = item.querySelector('.check'); if (c) c.style.display = '';
|
|
2512
|
-
}
|
|
2513
|
-
item.onclick = () => {
|
|
2514
|
-
if (isMulti) {
|
|
2515
|
-
Array.from(block.querySelectorAll('.action-sheet-item')).forEach((el) => {
|
|
2516
|
-
el.classList.remove('ask-opt-selected');
|
|
2517
|
-
const c = el.querySelector('.check'); if (c) c.style.display = 'none';
|
|
2518
|
-
});
|
|
2519
|
-
item.classList.add('ask-opt-selected');
|
|
2520
|
-
const c = item.querySelector('.check'); if (c) c.style.display = '';
|
|
2521
|
-
askMultiState.answers[qIdx] = { question: qText, label };
|
|
2522
|
-
refreshAskMultiSubmit();
|
|
2523
|
-
} else {
|
|
2524
|
-
_clearAsk();
|
|
2525
|
-
addInputMessage(label);
|
|
2526
|
-
socket.emit('agent:query', { prompt: label });
|
|
2527
|
-
showThinking();
|
|
2528
|
-
}
|
|
2529
|
-
};
|
|
2530
|
-
block.appendChild(item);
|
|
2531
|
-
});
|
|
2532
|
-
}
|
|
2533
|
-
body.appendChild(block);
|
|
2534
|
-
});
|
|
2535
|
-
|
|
2536
|
-
if (isMulti) refreshAskMultiSubmit();
|
|
2537
|
-
sheet.classList.add('open');
|
|
2538
|
-
if (!isMulti) inp.focus();
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
function refreshAskMultiSubmit() {
|
|
2542
|
-
const btn = document.getElementById('ask-sheet-submit-multi');
|
|
2543
|
-
if (!askMultiState) return;
|
|
2544
|
-
const n = _askQuestions.length;
|
|
2545
|
-
const remaining = n - askMultiState.answers.slice(0, n).filter(Boolean).length;
|
|
2546
|
-
btn.disabled = remaining > 0;
|
|
2547
|
-
btn.textContent = remaining > 0 ? '还剩 ' + remaining + ' 题未答' : '提交回答';
|
|
2548
|
-
}
|
|
2549
|
-
|
|
2550
|
-
function _clearAsk() {
|
|
2551
|
-
_askQuestions = [];
|
|
2552
|
-
askMultiState = null;
|
|
2553
|
-
const inp = document.getElementById('ask-sheet-input');
|
|
2554
|
-
if (inp) inp.value = '';
|
|
2555
|
-
document.getElementById('ask-sheet').classList.remove('open');
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
window.submitAskMulti = function() {
|
|
2559
|
-
if (!askMultiState) return;
|
|
2560
|
-
const n = _askQuestions.length;
|
|
2561
|
-
const answers = askMultiState.answers.slice(0, n);
|
|
2562
|
-
if (answers.some((a) => !a)) return;
|
|
2563
|
-
const reply = answers.map((a, i) => 'Q' + (i + 1) + ': ' + a.question + '\nA: ' + a.label).join('\n\n');
|
|
2564
|
-
_clearAsk();
|
|
2565
|
-
addInputMessage(reply);
|
|
2566
|
-
socket.emit('agent:query', { prompt: reply });
|
|
2567
|
-
showThinking();
|
|
2568
|
-
};
|
|
2569
|
-
window.closeAskSheet = function() { _clearAsk(); };
|
|
2570
|
-
window.submitAskReply = function() {
|
|
2571
|
-
const inp = document.getElementById('ask-sheet-input');
|
|
2572
|
-
const text = inp.value.trim();
|
|
2573
|
-
if (!text) return;
|
|
2574
|
-
_clearAsk();
|
|
2575
|
-
addInputMessage(text);
|
|
2576
|
-
socket.emit('agent:query', { prompt: text });
|
|
2577
|
-
showThinking();
|
|
2578
|
-
};
|
|
2579
|
-
document.getElementById('ask-sheet-input').addEventListener('keydown', (e) => {
|
|
2580
|
-
if (e.key === 'Enter') { e.preventDefault(); submitAskReply(); }
|
|
2581
|
-
});
|
|
2582
|
-
|
|
2441
|
+
// ── AskUserQuestion 弹窗(累加多题)──
|
|
2442
|
+
// AI 一轮里可能连发多个 AskUserQuestion 调用;旧逻辑每次 openAskSheet 都清空覆盖,
|
|
2443
|
+
// 导致只看到最后一题、其余被顶掉无法回答。改为把本轮所有提问累加进同一弹窗,
|
|
2444
|
+
// 多题一次答完;跨重渲染保留已选答案;去重防同一题被 assistant 事件+独立 tool_use 事件各触发一次。
|
|
2445
|
+
let _askQuestions = [];
|
|
2446
|
+
let askMultiState = null; // { answers: [{question,label}|null] },下标与 _askQuestions 对齐
|
|
2447
|
+
|
|
2448
|
+
function _normalizeAsk(input) {
|
|
2449
|
+
const q = input?.questions || input?.question;
|
|
2450
|
+
const list = Array.isArray(q)
|
|
2451
|
+
? q
|
|
2452
|
+
: [{ question: (typeof q === 'string' ? q : '') || input?.question || '请选择', options: input?.options, header: input?.header }];
|
|
2453
|
+
return list.map((x) => ({
|
|
2454
|
+
question: x.question || x.text || '',
|
|
2455
|
+
options: x.options || x.choices || [],
|
|
2456
|
+
header: x.header,
|
|
2457
|
+
}));
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
window.openAskSheet = function(input) {
|
|
2461
|
+
for (const q of _normalizeAsk(input)) {
|
|
2462
|
+
if (!q.question || !_askQuestions.some((e) => e.question === q.question)) _askQuestions.push(q);
|
|
2463
|
+
}
|
|
2464
|
+
if (!_askQuestions.length) return;
|
|
2465
|
+
_renderAskSheet();
|
|
2466
|
+
};
|
|
2467
|
+
|
|
2468
|
+
function _renderAskSheet() {
|
|
2469
|
+
const sheet = document.getElementById('ask-sheet');
|
|
2470
|
+
const title = document.getElementById('ask-sheet-title');
|
|
2471
|
+
const body = document.getElementById('ask-sheet-body');
|
|
2472
|
+
const inp = document.getElementById('ask-sheet-input');
|
|
2473
|
+
const inputWrap = document.getElementById('ask-sheet-input-wrap');
|
|
2474
|
+
const btnMulti = document.getElementById('ask-sheet-submit-multi');
|
|
2475
|
+
const btnSingle = document.getElementById('ask-sheet-submit-single');
|
|
2476
|
+
|
|
2477
|
+
const qList = _askQuestions;
|
|
2478
|
+
const isMulti = qList.length > 1;
|
|
2479
|
+
if (!askMultiState) askMultiState = { answers: [] };
|
|
2480
|
+
while (askMultiState.answers.length < qList.length) askMultiState.answers.push(null);
|
|
2481
|
+
|
|
2482
|
+
title.textContent = (qList[0] && qList[0].header) || (isMulti ? '提问(' + qList.length + ' 题)' : '提问');
|
|
2483
|
+
if (isMulti) {
|
|
2484
|
+
inputWrap.style.display = 'none';
|
|
2485
|
+
btnSingle.style.display = 'none';
|
|
2486
|
+
btnMulti.style.display = '';
|
|
2487
|
+
} else {
|
|
2488
|
+
inputWrap.style.display = '';
|
|
2489
|
+
btnSingle.style.display = '';
|
|
2490
|
+
btnMulti.style.display = 'none';
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
body.innerHTML = '';
|
|
2494
|
+
qList.forEach((q, qIdx) => {
|
|
2495
|
+
const qText = q.question || '';
|
|
2496
|
+
const block = document.createElement('div');
|
|
2497
|
+
block.className = isMulti ? 'ask-q-block' : '';
|
|
2498
|
+
if (isMulti) {
|
|
2499
|
+
const label = document.createElement('div');
|
|
2500
|
+
label.className = 'ask-q-label';
|
|
2501
|
+
label.textContent = 'Q' + (qIdx + 1);
|
|
2502
|
+
block.appendChild(label);
|
|
2503
|
+
}
|
|
2504
|
+
if (qText) {
|
|
2505
|
+
const desc = document.createElement('div');
|
|
2506
|
+
if (isMulti) { desc.className = 'ask-q-text'; desc.textContent = qText; }
|
|
2507
|
+
else { desc.style.cssText = 'padding:0.5rem 0.3rem;font-size:0.82rem;color:#c9d1d9;line-height:1.5;white-space:pre-wrap'; desc.textContent = qText; }
|
|
2508
|
+
block.appendChild(desc);
|
|
2509
|
+
}
|
|
2510
|
+
if (q.options?.length) {
|
|
2511
|
+
q.options.forEach((opt) => {
|
|
2512
|
+
const item = document.createElement('div');
|
|
2513
|
+
item.className = 'action-sheet-item';
|
|
2514
|
+
const label = opt.label || opt.value || String(opt);
|
|
2515
|
+
item.innerHTML = '<div><div>' + escHtml(label) + '</div>' + (opt.description ? '<div class="desc">' + escHtml(opt.description) + '</div>' : '') + '</div><span class="check" style="display:none">✓</span>';
|
|
2516
|
+
if (isMulti && askMultiState.answers[qIdx]?.label === label) {
|
|
2517
|
+
item.classList.add('ask-opt-selected');
|
|
2518
|
+
const c = item.querySelector('.check'); if (c) c.style.display = '';
|
|
2519
|
+
}
|
|
2520
|
+
item.onclick = () => {
|
|
2521
|
+
if (isMulti) {
|
|
2522
|
+
Array.from(block.querySelectorAll('.action-sheet-item')).forEach((el) => {
|
|
2523
|
+
el.classList.remove('ask-opt-selected');
|
|
2524
|
+
const c = el.querySelector('.check'); if (c) c.style.display = 'none';
|
|
2525
|
+
});
|
|
2526
|
+
item.classList.add('ask-opt-selected');
|
|
2527
|
+
const c = item.querySelector('.check'); if (c) c.style.display = '';
|
|
2528
|
+
askMultiState.answers[qIdx] = { question: qText, label };
|
|
2529
|
+
refreshAskMultiSubmit();
|
|
2530
|
+
} else {
|
|
2531
|
+
_clearAsk();
|
|
2532
|
+
addInputMessage(label);
|
|
2533
|
+
socket.emit('agent:query', { prompt: label });
|
|
2534
|
+
showThinking();
|
|
2535
|
+
}
|
|
2536
|
+
};
|
|
2537
|
+
block.appendChild(item);
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
body.appendChild(block);
|
|
2541
|
+
});
|
|
2542
|
+
|
|
2543
|
+
if (isMulti) refreshAskMultiSubmit();
|
|
2544
|
+
sheet.classList.add('open');
|
|
2545
|
+
if (!isMulti) inp.focus();
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function refreshAskMultiSubmit() {
|
|
2549
|
+
const btn = document.getElementById('ask-sheet-submit-multi');
|
|
2550
|
+
if (!askMultiState) return;
|
|
2551
|
+
const n = _askQuestions.length;
|
|
2552
|
+
const remaining = n - askMultiState.answers.slice(0, n).filter(Boolean).length;
|
|
2553
|
+
btn.disabled = remaining > 0;
|
|
2554
|
+
btn.textContent = remaining > 0 ? '还剩 ' + remaining + ' 题未答' : '提交回答';
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
function _clearAsk() {
|
|
2558
|
+
_askQuestions = [];
|
|
2559
|
+
askMultiState = null;
|
|
2560
|
+
const inp = document.getElementById('ask-sheet-input');
|
|
2561
|
+
if (inp) inp.value = '';
|
|
2562
|
+
document.getElementById('ask-sheet').classList.remove('open');
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
window.submitAskMulti = function() {
|
|
2566
|
+
if (!askMultiState) return;
|
|
2567
|
+
const n = _askQuestions.length;
|
|
2568
|
+
const answers = askMultiState.answers.slice(0, n);
|
|
2569
|
+
if (answers.some((a) => !a)) return;
|
|
2570
|
+
const reply = answers.map((a, i) => 'Q' + (i + 1) + ': ' + a.question + '\nA: ' + a.label).join('\n\n');
|
|
2571
|
+
_clearAsk();
|
|
2572
|
+
addInputMessage(reply);
|
|
2573
|
+
socket.emit('agent:query', { prompt: reply });
|
|
2574
|
+
showThinking();
|
|
2575
|
+
};
|
|
2576
|
+
window.closeAskSheet = function() { _clearAsk(); };
|
|
2577
|
+
window.submitAskReply = function() {
|
|
2578
|
+
const inp = document.getElementById('ask-sheet-input');
|
|
2579
|
+
const text = inp.value.trim();
|
|
2580
|
+
if (!text) return;
|
|
2581
|
+
_clearAsk();
|
|
2582
|
+
addInputMessage(text);
|
|
2583
|
+
socket.emit('agent:query', { prompt: text });
|
|
2584
|
+
showThinking();
|
|
2585
|
+
};
|
|
2586
|
+
document.getElementById('ask-sheet-input').addEventListener('keydown', (e) => {
|
|
2587
|
+
if (e.key === 'Enter') { e.preventDefault(); submitAskReply(); }
|
|
2588
|
+
});
|
|
2589
|
+
|
|
2583
2590
|
window.openModelSheet = function() {
|
|
2584
2591
|
const list = document.getElementById('model-list');
|
|
2585
2592
|
list.innerHTML = '';
|