@wendongfly/myhi 1.3.45 → 1.3.47
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 +123 -32
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -237,6 +237,13 @@
|
|
|
237
237
|
.action-sheet-item:active { background: #30363d; }
|
|
238
238
|
.action-sheet-item .desc { font-size: 0.72rem; color: #8b949e; }
|
|
239
239
|
.action-sheet-item .check { color: #3fb950; font-size: 0.9rem; }
|
|
240
|
+
.ask-q-block { padding: 0.5rem 0.3rem 0.3rem; border-top: 1px solid #21262d; }
|
|
241
|
+
.ask-q-block:first-child { border-top: none; }
|
|
242
|
+
.ask-q-label { font-size: 0.72rem; color: #8b949e; font-weight: 600; margin-bottom: 0.25rem; letter-spacing: 0.05em; }
|
|
243
|
+
.ask-q-text { font-size: 0.82rem; color: #c9d1d9; line-height: 1.5; white-space: pre-wrap; margin-bottom: 0.4rem; }
|
|
244
|
+
.ask-opt-selected { background: #1f3a5f !important; border: 1px solid #388bfd; }
|
|
245
|
+
.ask-opt-selected .check { color: #58a6ff; }
|
|
246
|
+
.ask-submit-btn:disabled { background: #30363d !important; color: #6e7681 !important; cursor: not-allowed; }
|
|
240
247
|
.action-sheet-cancel { display: block; width: 100%; margin-top: 0.5rem; padding: 0.65rem; background: #21262d; border: none; border-radius: 10px; color: #8b949e; font-size: 0.85rem; cursor: pointer; text-align: center; }
|
|
241
248
|
.mem-file { margin-bottom: 0.5rem; border: 1px solid #21262d; border-radius: 8px; overflow: hidden; }
|
|
242
249
|
.mem-file-header { display: flex; align-items: center; gap: 0.35rem; padding: 0.4rem 0.6rem; background: #21262d; cursor: pointer; font-size: 0.75rem; color: #58a6ff; user-select: none; }
|
|
@@ -493,10 +500,11 @@
|
|
|
493
500
|
<div class="action-sheet-box" style="max-height:70vh;display:flex;flex-direction:column">
|
|
494
501
|
<div class="action-sheet-title" id="ask-sheet-title">提问</div>
|
|
495
502
|
<div id="ask-sheet-body" style="flex:1;overflow-y:auto;padding:0.3rem 0;scrollbar-width:thin"></div>
|
|
496
|
-
<div style="padding:0.3rem 0.2rem">
|
|
503
|
+
<div id="ask-sheet-input-wrap" style="padding:0.3rem 0.2rem">
|
|
497
504
|
<input id="ask-sheet-input" class="slash-inp" placeholder="输入自定义回答(可选)" autocomplete="off">
|
|
498
505
|
</div>
|
|
499
|
-
<button class="action-sheet-cancel" style="background:#
|
|
506
|
+
<button id="ask-sheet-submit-multi" class="action-sheet-cancel ask-submit-btn" style="display:none;background:#388bfd;color:#fff;font-weight:600;margin-bottom:0.4rem" onclick="submitAskMulti()">提交回答</button>
|
|
507
|
+
<button id="ask-sheet-submit-single" class="action-sheet-cancel" style="background:#7c3aed;color:#fff;font-weight:600;margin-bottom:0.4rem" onclick="submitAskReply()">发送自定义回答</button>
|
|
500
508
|
<button class="action-sheet-cancel" onclick="closeAskSheet()">取消</button>
|
|
501
509
|
</div>
|
|
502
510
|
</div>
|
|
@@ -800,7 +808,8 @@
|
|
|
800
808
|
// ── 消息渲染 ──────────────────────────────────
|
|
801
809
|
function addInputMessage(text) {
|
|
802
810
|
endStream(); endToolGroup();
|
|
803
|
-
|
|
811
|
+
_streamedTextThisCall = false;
|
|
812
|
+
_textRenderedThisTurn = false;
|
|
804
813
|
const msg = document.createElement('div');
|
|
805
814
|
msg.className = 'msg msg-input';
|
|
806
815
|
msg.innerHTML = `<div><div class="bubble">${escHtml(text)}</div><div class="meta">${formatTime(new Date())}</div></div>`;
|
|
@@ -821,7 +830,14 @@
|
|
|
821
830
|
|
|
822
831
|
let _streamEl = null; // 当前流式输出的 assistant 元素
|
|
823
832
|
let _streamText = ''; // 累积的原始文本
|
|
824
|
-
|
|
833
|
+
// 拆成两个标志解耦:
|
|
834
|
+
// _streamedTextThisCall per-LLM-call:当前 LLM call 内是否已通过 content_block_delta 渲染过文本,
|
|
835
|
+
// 用于抑制同一 call 末尾 assistant 事件 text 块的重复回声。call 边界由 message_start / tool_result 重置。
|
|
836
|
+
// _textRenderedThisTurn per-turn:当前轮(自上次用户提问起)是否已渲染过任何答复文本,
|
|
837
|
+
// 用于抑制 result 事件的 msg.result 重复渲染。
|
|
838
|
+
// 之前合并成一个 per-turn 标志会让"非流式 + 多步工具调用"场景里第二条 assistant 事件的 text 被吃掉。
|
|
839
|
+
let _streamedTextThisCall = false;
|
|
840
|
+
let _textRenderedThisTurn = false;
|
|
825
841
|
|
|
826
842
|
function addAssistantMessage(raw) {
|
|
827
843
|
removeThinking(); endToolGroup();
|
|
@@ -1316,15 +1332,16 @@
|
|
|
1316
1332
|
case 'assistant':
|
|
1317
1333
|
removeThinking();
|
|
1318
1334
|
if (msg.message?.content) {
|
|
1335
|
+
// 进入 loop 前快照:同一 assistant 事件里所有 text 块统一按入口时刻的 call 标志决定,
|
|
1336
|
+
// 避免渲染完第一段 text 后置位标志,导致同事件内 [text, tool_use, text] 的第二段被跳过
|
|
1337
|
+
const skipText = _streamedTextThisCall;
|
|
1319
1338
|
for (const block of msg.message.content) {
|
|
1320
1339
|
if (block.type === 'thinking' && block.thinking) {
|
|
1321
1340
|
showThinking(block.thinking);
|
|
1322
1341
|
} else if (block.type === 'text' && block.text) {
|
|
1323
|
-
if (!
|
|
1342
|
+
if (!skipText) {
|
|
1324
1343
|
addAssistantMessage(block.text);
|
|
1325
|
-
|
|
1326
|
-
// 这里渲染后也要置位,避免后续 result 事件再渲染同样的文本
|
|
1327
|
-
_streamedTextThisMessage = true;
|
|
1344
|
+
_textRenderedThisTurn = true;
|
|
1328
1345
|
}
|
|
1329
1346
|
} else if (block.type === 'tool_use') {
|
|
1330
1347
|
if (block.name === 'AskUserQuestion' && block.input) {
|
|
@@ -1342,9 +1359,9 @@
|
|
|
1342
1359
|
case 'result':
|
|
1343
1360
|
removeThinking();
|
|
1344
1361
|
setWorkState('idle');
|
|
1345
|
-
// msg.result
|
|
1346
|
-
//
|
|
1347
|
-
if (msg.result && !
|
|
1362
|
+
// msg.result 是整轮的最终答复文本,纯文字回答时与本轮已渲染的文本重复;
|
|
1363
|
+
// 本轮已经通过流式或 assistant 事件渲染过就跳过,避免生成第二个独立气泡
|
|
1364
|
+
if (msg.result && !_textRenderedThisTurn) {
|
|
1348
1365
|
addAssistantMessage(typeof msg.result === 'string' ? msg.result : JSON.stringify(msg.result));
|
|
1349
1366
|
}
|
|
1350
1367
|
const cost = msg.total_cost_usd ? ` ($${msg.total_cost_usd.toFixed(4)})` : '';
|
|
@@ -1362,7 +1379,8 @@
|
|
|
1362
1379
|
// 流式内容增量
|
|
1363
1380
|
if (msg.delta?.type === 'text_delta' && msg.delta.text) {
|
|
1364
1381
|
appendStream(msg.delta.text);
|
|
1365
|
-
|
|
1382
|
+
_streamedTextThisCall = true;
|
|
1383
|
+
_textRenderedThisTurn = true;
|
|
1366
1384
|
} else if (msg.delta?.type === 'thinking_delta' && msg.delta.thinking) {
|
|
1367
1385
|
appendThinking(msg.delta.thinking);
|
|
1368
1386
|
} else if (msg.delta?.type === 'input_json_delta' && msg.delta.partial_json) {
|
|
@@ -1373,6 +1391,8 @@
|
|
|
1373
1391
|
endStream();
|
|
1374
1392
|
break;
|
|
1375
1393
|
case 'message_start':
|
|
1394
|
+
// 新 LLM call 边界:重置 call 标志,让本 call 的 assistant 文本能正常渲染(同一轮内可能有多次 call)
|
|
1395
|
+
_streamedTextThisCall = false;
|
|
1376
1396
|
setWorkState('working');
|
|
1377
1397
|
break;
|
|
1378
1398
|
case 'message_delta':
|
|
@@ -1389,7 +1409,9 @@
|
|
|
1389
1409
|
setWorkState('working');
|
|
1390
1410
|
break;
|
|
1391
1411
|
case 'tool_result':
|
|
1392
|
-
//
|
|
1412
|
+
// 独立工具结果消息。同时作为 call 边界兜底:如果某适配器没发 message_start,
|
|
1413
|
+
// 这里重置 call 标志能保证后续 assistant 文本不被前一个 call 的标志误吃
|
|
1414
|
+
_streamedTextThisCall = false;
|
|
1393
1415
|
if (msg.content) {
|
|
1394
1416
|
const text = typeof msg.content === 'string' ? msg.content
|
|
1395
1417
|
: Array.isArray(msg.content) ? msg.content.map(b => b.text || '').join('\n')
|
|
@@ -1459,7 +1481,9 @@
|
|
|
1459
1481
|
el.appendChild(content);
|
|
1460
1482
|
chatArea.appendChild(el);
|
|
1461
1483
|
_streamEl = el;
|
|
1462
|
-
|
|
1484
|
+
// 还原后的气泡相当于"本 call 已渲染过文本",避免回放完成后紧接着又收到的 assistant 事件再渲一次
|
|
1485
|
+
_streamedTextThisCall = true;
|
|
1486
|
+
_textRenderedThisTurn = true;
|
|
1463
1487
|
}
|
|
1464
1488
|
|
|
1465
1489
|
scrollToBottom();
|
|
@@ -1953,47 +1977,114 @@
|
|
|
1953
1977
|
let currentModelId = '';
|
|
1954
1978
|
|
|
1955
1979
|
// ── AskUserQuestion 弹窗 ──
|
|
1980
|
+
// 多题模式状态:{ qList, answers: [{ question, label } | null] }
|
|
1981
|
+
let askMultiState = null;
|
|
1956
1982
|
window.openAskSheet = function(input) {
|
|
1957
1983
|
const questions = input?.questions || input?.question;
|
|
1958
1984
|
const sheet = document.getElementById('ask-sheet');
|
|
1959
1985
|
const title = document.getElementById('ask-sheet-title');
|
|
1960
1986
|
const body = document.getElementById('ask-sheet-body');
|
|
1961
1987
|
const inp = document.getElementById('ask-sheet-input');
|
|
1988
|
+
const inputWrap = document.getElementById('ask-sheet-input-wrap');
|
|
1989
|
+
const btnMulti = document.getElementById('ask-sheet-submit-multi');
|
|
1990
|
+
const btnSingle = document.getElementById('ask-sheet-submit-single');
|
|
1962
1991
|
body.innerHTML = '';
|
|
1963
1992
|
inp.value = '';
|
|
1964
1993
|
|
|
1965
|
-
//
|
|
1966
|
-
const
|
|
1994
|
+
// 区分单题/多题。questions 是非空数组且 length>1 才进入批量模式
|
|
1995
|
+
const isMulti = Array.isArray(questions) && questions.length > 1;
|
|
1996
|
+
const qList = Array.isArray(questions)
|
|
1997
|
+
? questions
|
|
1998
|
+
: [{ question: questions || input?.question || '请选择', options: input?.options, header: input?.header }];
|
|
1999
|
+
|
|
2000
|
+
// 标题:多题用首题 header 或通用文案,单题保持原行为
|
|
2001
|
+
title.textContent = (qList[0] && qList[0].header) || input?.header || (isMulti ? `提问(${qList.length} 题)` : '提问');
|
|
1967
2002
|
|
|
1968
|
-
|
|
2003
|
+
if (isMulti) {
|
|
2004
|
+
askMultiState = { qList, answers: new Array(qList.length).fill(null) };
|
|
2005
|
+
inputWrap.style.display = 'none';
|
|
2006
|
+
btnSingle.style.display = 'none';
|
|
2007
|
+
btnMulti.style.display = '';
|
|
2008
|
+
} else {
|
|
2009
|
+
askMultiState = null;
|
|
2010
|
+
inputWrap.style.display = '';
|
|
2011
|
+
btnSingle.style.display = '';
|
|
2012
|
+
btnMulti.style.display = 'none';
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
qList.forEach((q, qIdx) => {
|
|
1969
2016
|
const qText = q.question || q.text || '';
|
|
1970
|
-
|
|
1971
|
-
|
|
2017
|
+
const block = document.createElement('div');
|
|
2018
|
+
block.className = isMulti ? 'ask-q-block' : '';
|
|
2019
|
+
|
|
2020
|
+
if (isMulti) {
|
|
2021
|
+
const label = document.createElement('div');
|
|
2022
|
+
label.className = 'ask-q-label';
|
|
2023
|
+
label.textContent = `Q${qIdx + 1}`;
|
|
2024
|
+
block.appendChild(label);
|
|
2025
|
+
}
|
|
1972
2026
|
if (qText) {
|
|
1973
2027
|
const desc = document.createElement('div');
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2028
|
+
if (isMulti) {
|
|
2029
|
+
desc.className = 'ask-q-text';
|
|
2030
|
+
desc.textContent = qText;
|
|
2031
|
+
} else {
|
|
2032
|
+
desc.style.cssText = 'padding:0.5rem 0.3rem;font-size:0.82rem;color:#c9d1d9;line-height:1.5;white-space:pre-wrap';
|
|
2033
|
+
desc.textContent = qText;
|
|
2034
|
+
}
|
|
2035
|
+
block.appendChild(desc);
|
|
1977
2036
|
}
|
|
1978
|
-
// 选项列表
|
|
1979
2037
|
if (q.options?.length) {
|
|
1980
|
-
q.options.forEach(opt => {
|
|
2038
|
+
q.options.forEach((opt, optIdx) => {
|
|
1981
2039
|
const item = document.createElement('div');
|
|
1982
2040
|
item.className = 'action-sheet-item';
|
|
1983
|
-
|
|
2041
|
+
const label = opt.label || opt.value || String(opt);
|
|
2042
|
+
item.innerHTML = `<div><div>${escHtml(label)}</div>${opt.description ? `<div class="desc">${escHtml(opt.description)}</div>` : ''}</div><span class="check" style="display:none">✓</span>`;
|
|
1984
2043
|
item.onclick = () => {
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
2044
|
+
if (isMulti) {
|
|
2045
|
+
// 单选切换:清掉同题其它选中态,标记当前
|
|
2046
|
+
Array.from(block.querySelectorAll('.action-sheet-item')).forEach(el => {
|
|
2047
|
+
el.classList.remove('ask-opt-selected');
|
|
2048
|
+
const c = el.querySelector('.check'); if (c) c.style.display = 'none';
|
|
2049
|
+
});
|
|
2050
|
+
item.classList.add('ask-opt-selected');
|
|
2051
|
+
const c = item.querySelector('.check'); if (c) c.style.display = '';
|
|
2052
|
+
askMultiState.answers[qIdx] = { question: qText, label };
|
|
2053
|
+
refreshAskMultiSubmit();
|
|
2054
|
+
} else {
|
|
2055
|
+
closeAskSheet();
|
|
2056
|
+
addInputMessage(label);
|
|
2057
|
+
socket.emit('agent:query', { prompt: label });
|
|
2058
|
+
showThinking();
|
|
2059
|
+
}
|
|
1990
2060
|
};
|
|
1991
|
-
|
|
2061
|
+
block.appendChild(item);
|
|
1992
2062
|
});
|
|
1993
2063
|
}
|
|
2064
|
+
body.appendChild(block);
|
|
1994
2065
|
});
|
|
2066
|
+
|
|
2067
|
+
if (isMulti) refreshAskMultiSubmit();
|
|
1995
2068
|
sheet.classList.add('open');
|
|
1996
|
-
inp.focus();
|
|
2069
|
+
if (!isMulti) inp.focus();
|
|
2070
|
+
};
|
|
2071
|
+
function refreshAskMultiSubmit() {
|
|
2072
|
+
const btn = document.getElementById('ask-sheet-submit-multi');
|
|
2073
|
+
if (!askMultiState) return;
|
|
2074
|
+
const remaining = askMultiState.answers.filter(a => !a).length;
|
|
2075
|
+
btn.disabled = remaining > 0;
|
|
2076
|
+
btn.textContent = remaining > 0 ? `还剩 ${remaining} 题未答` : '提交回答';
|
|
2077
|
+
}
|
|
2078
|
+
window.submitAskMulti = function() {
|
|
2079
|
+
if (!askMultiState) return;
|
|
2080
|
+
const { answers } = askMultiState;
|
|
2081
|
+
if (answers.some(a => !a)) return;
|
|
2082
|
+
const reply = answers.map((a, i) => `Q${i + 1}: ${a.question}\nA: ${a.label}`).join('\n\n');
|
|
2083
|
+
closeAskSheet();
|
|
2084
|
+
askMultiState = null;
|
|
2085
|
+
addInputMessage(reply);
|
|
2086
|
+
socket.emit('agent:query', { prompt: reply });
|
|
2087
|
+
showThinking();
|
|
1997
2088
|
};
|
|
1998
2089
|
window.closeAskSheet = function() {
|
|
1999
2090
|
document.getElementById('ask-sheet').classList.remove('open');
|