@wendongfly/myhi 1.3.44 → 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 +125 -29
- 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,11 +1332,17 @@
|
|
|
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) {
|
|
1343
|
+
addAssistantMessage(block.text);
|
|
1344
|
+
_textRenderedThisTurn = true;
|
|
1345
|
+
}
|
|
1324
1346
|
} else if (block.type === 'tool_use') {
|
|
1325
1347
|
if (block.name === 'AskUserQuestion' && block.input) {
|
|
1326
1348
|
openAskSheet(block.input);
|
|
@@ -1337,9 +1359,9 @@
|
|
|
1337
1359
|
case 'result':
|
|
1338
1360
|
removeThinking();
|
|
1339
1361
|
setWorkState('idle');
|
|
1340
|
-
// msg.result
|
|
1341
|
-
//
|
|
1342
|
-
if (msg.result && !
|
|
1362
|
+
// msg.result 是整轮的最终答复文本,纯文字回答时与本轮已渲染的文本重复;
|
|
1363
|
+
// 本轮已经通过流式或 assistant 事件渲染过就跳过,避免生成第二个独立气泡
|
|
1364
|
+
if (msg.result && !_textRenderedThisTurn) {
|
|
1343
1365
|
addAssistantMessage(typeof msg.result === 'string' ? msg.result : JSON.stringify(msg.result));
|
|
1344
1366
|
}
|
|
1345
1367
|
const cost = msg.total_cost_usd ? ` ($${msg.total_cost_usd.toFixed(4)})` : '';
|
|
@@ -1357,7 +1379,8 @@
|
|
|
1357
1379
|
// 流式内容增量
|
|
1358
1380
|
if (msg.delta?.type === 'text_delta' && msg.delta.text) {
|
|
1359
1381
|
appendStream(msg.delta.text);
|
|
1360
|
-
|
|
1382
|
+
_streamedTextThisCall = true;
|
|
1383
|
+
_textRenderedThisTurn = true;
|
|
1361
1384
|
} else if (msg.delta?.type === 'thinking_delta' && msg.delta.thinking) {
|
|
1362
1385
|
appendThinking(msg.delta.thinking);
|
|
1363
1386
|
} else if (msg.delta?.type === 'input_json_delta' && msg.delta.partial_json) {
|
|
@@ -1368,6 +1391,8 @@
|
|
|
1368
1391
|
endStream();
|
|
1369
1392
|
break;
|
|
1370
1393
|
case 'message_start':
|
|
1394
|
+
// 新 LLM call 边界:重置 call 标志,让本 call 的 assistant 文本能正常渲染(同一轮内可能有多次 call)
|
|
1395
|
+
_streamedTextThisCall = false;
|
|
1371
1396
|
setWorkState('working');
|
|
1372
1397
|
break;
|
|
1373
1398
|
case 'message_delta':
|
|
@@ -1384,7 +1409,9 @@
|
|
|
1384
1409
|
setWorkState('working');
|
|
1385
1410
|
break;
|
|
1386
1411
|
case 'tool_result':
|
|
1387
|
-
//
|
|
1412
|
+
// 独立工具结果消息。同时作为 call 边界兜底:如果某适配器没发 message_start,
|
|
1413
|
+
// 这里重置 call 标志能保证后续 assistant 文本不被前一个 call 的标志误吃
|
|
1414
|
+
_streamedTextThisCall = false;
|
|
1388
1415
|
if (msg.content) {
|
|
1389
1416
|
const text = typeof msg.content === 'string' ? msg.content
|
|
1390
1417
|
: Array.isArray(msg.content) ? msg.content.map(b => b.text || '').join('\n')
|
|
@@ -1454,7 +1481,9 @@
|
|
|
1454
1481
|
el.appendChild(content);
|
|
1455
1482
|
chatArea.appendChild(el);
|
|
1456
1483
|
_streamEl = el;
|
|
1457
|
-
|
|
1484
|
+
// 还原后的气泡相当于"本 call 已渲染过文本",避免回放完成后紧接着又收到的 assistant 事件再渲一次
|
|
1485
|
+
_streamedTextThisCall = true;
|
|
1486
|
+
_textRenderedThisTurn = true;
|
|
1458
1487
|
}
|
|
1459
1488
|
|
|
1460
1489
|
scrollToBottom();
|
|
@@ -1948,47 +1977,114 @@
|
|
|
1948
1977
|
let currentModelId = '';
|
|
1949
1978
|
|
|
1950
1979
|
// ── AskUserQuestion 弹窗 ──
|
|
1980
|
+
// 多题模式状态:{ qList, answers: [{ question, label } | null] }
|
|
1981
|
+
let askMultiState = null;
|
|
1951
1982
|
window.openAskSheet = function(input) {
|
|
1952
1983
|
const questions = input?.questions || input?.question;
|
|
1953
1984
|
const sheet = document.getElementById('ask-sheet');
|
|
1954
1985
|
const title = document.getElementById('ask-sheet-title');
|
|
1955
1986
|
const body = document.getElementById('ask-sheet-body');
|
|
1956
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');
|
|
1957
1991
|
body.innerHTML = '';
|
|
1958
1992
|
inp.value = '';
|
|
1959
1993
|
|
|
1960
|
-
//
|
|
1961
|
-
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} 题)` : '提问');
|
|
2002
|
+
|
|
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
|
+
}
|
|
1962
2014
|
|
|
1963
|
-
qList.forEach(q => {
|
|
2015
|
+
qList.forEach((q, qIdx) => {
|
|
1964
2016
|
const qText = q.question || q.text || '';
|
|
1965
|
-
|
|
1966
|
-
|
|
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
|
+
}
|
|
1967
2026
|
if (qText) {
|
|
1968
2027
|
const desc = document.createElement('div');
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
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);
|
|
1972
2036
|
}
|
|
1973
|
-
// 选项列表
|
|
1974
2037
|
if (q.options?.length) {
|
|
1975
|
-
q.options.forEach(opt => {
|
|
2038
|
+
q.options.forEach((opt, optIdx) => {
|
|
1976
2039
|
const item = document.createElement('div');
|
|
1977
2040
|
item.className = 'action-sheet-item';
|
|
1978
|
-
|
|
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>`;
|
|
1979
2043
|
item.onclick = () => {
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
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
|
+
}
|
|
1985
2060
|
};
|
|
1986
|
-
|
|
2061
|
+
block.appendChild(item);
|
|
1987
2062
|
});
|
|
1988
2063
|
}
|
|
2064
|
+
body.appendChild(block);
|
|
1989
2065
|
});
|
|
2066
|
+
|
|
2067
|
+
if (isMulti) refreshAskMultiSubmit();
|
|
1990
2068
|
sheet.classList.add('open');
|
|
1991
|
-
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();
|
|
1992
2088
|
};
|
|
1993
2089
|
window.closeAskSheet = function() {
|
|
1994
2090
|
document.getElementById('ask-sheet').classList.remove('open');
|