@wendongfly/myhi 1.3.129 → 1.3.132
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 +194 -11
- package/dist/index.js +1 -1
- package/dist/index.min.js +5 -5
- package/installer/README.md +1 -1
- package/installer/install-myhi.ps1 +14 -0
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -134,6 +134,25 @@
|
|
|
134
134
|
.tool-body { display: none; background: var(--surface); border: 1px solid var(--surface2); border-radius: 6px; padding: 0.5rem; margin-top: 0.2rem; font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.75rem; line-height: 1.3; overflow-x: auto; white-space: pre-wrap; word-break: break-all; max-height: 300px; overflow-y: auto; }
|
|
135
135
|
.tool-body.open { display: block; }
|
|
136
136
|
|
|
137
|
+
/* 子代理(Task)面板:内部活动按 parent_tool_use_id 归拢 + 状态灯,不平铺污染主流 */
|
|
138
|
+
.msg-subagent { border-left: 3px solid var(--accent); margin: 0.4rem 0 0.4rem 0.3rem; padding-left: 0.6rem; }
|
|
139
|
+
.sub-header { display: flex; align-items: center; gap: 0.4rem; cursor: pointer; font-size: 0.8rem; color: var(--accent); padding: 0.28rem 0; user-select: none; flex-wrap: wrap; }
|
|
140
|
+
.sub-header:hover { filter: brightness(1.15); }
|
|
141
|
+
.sub-header .arrow { transition: transform 0.15s; font-size: 0.65rem; }
|
|
142
|
+
.sub-header .arrow.open { transform: rotate(90deg); }
|
|
143
|
+
.sub-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
|
|
144
|
+
.sub-dot.running { background: var(--blue); animation: subpulse 1s ease-in-out infinite; }
|
|
145
|
+
.sub-dot.done { background: var(--green); }
|
|
146
|
+
.sub-dot.fail { background: #e5484d; }
|
|
147
|
+
@keyframes subpulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.3;transform:scale(.65)} }
|
|
148
|
+
.sub-meta { color: var(--muted); font-size: 0.72rem; }
|
|
149
|
+
.sub-body { display: none; margin-top: 0.2rem; padding-left: 0.4rem; border-left: 1px dashed var(--surface2); }
|
|
150
|
+
.sub-body.open { display: block; }
|
|
151
|
+
.sub-desc { color: var(--muted); font-size: 0.74rem; padding: 0.2rem 0 0.35rem; white-space: pre-wrap; word-break: break-word; }
|
|
152
|
+
.sub-act { font-size: 0.75rem; padding: 0.15rem 0; color: var(--text); white-space: pre-wrap; word-break: break-word; }
|
|
153
|
+
.sub-act .k { color: var(--muted); }
|
|
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
|
+
|
|
137
156
|
|
|
138
157
|
/* Diff 视图 */
|
|
139
158
|
.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; }
|
|
@@ -428,6 +447,7 @@
|
|
|
428
447
|
<button class="sk sk-claude" onclick="doClear()">清除</button>
|
|
429
448
|
<button class="sk sk-claude" onclick="openSecretsSheet()" style="color:var(--blue2)">🔑 密钥</button>
|
|
430
449
|
<button class="sk sk-claude" onclick="openGuardSheet()" style="color:var(--blue2)">🛡 护栏</button>
|
|
450
|
+
<button class="sk sk-claude" onclick="openFollowUpSheet()" style="color:var(--blue2)">🔄 跟进</button>
|
|
431
451
|
<button class="sk sk-claude" onclick="openGitSheet()" style="color:var(--green)">提交</button>
|
|
432
452
|
<button class="sk sk-claude" onclick="showMemory()">记忆</button>
|
|
433
453
|
<button class="sk sk-claude" onclick="openVaSheet()" style="color:var(--blue2)">语音</button>
|
|
@@ -676,6 +696,33 @@
|
|
|
676
696
|
</div>
|
|
677
697
|
</div>
|
|
678
698
|
|
|
699
|
+
<!-- 自动跟进(会话级):长任务完成后自动汇报进度,免用户反复问 -->
|
|
700
|
+
<div id="followup-sheet" class="action-sheet">
|
|
701
|
+
<div class="action-sheet-backdrop" onclick="closeFollowUpSheet()"></div>
|
|
702
|
+
<div class="action-sheet-box" style="max-height:72vh;display:flex;flex-direction:column">
|
|
703
|
+
<div class="action-sheet-title">🔄 自动跟进 · 本会话</div>
|
|
704
|
+
<div style="font-size:0.68rem;color:var(--muted);padding:0 0.2rem 0.5rem;line-height:1.55">
|
|
705
|
+
交给 AI 的长任务(压缩、导入、批处理…)常要你反复问"好了没"。开启后,AI 说完一轮、会话空闲时,myhi 会<b>每隔一段时间自动替你问一次进度并让 AI 汇报</b>;AI 判断<b>全部完成会自动停</b>。
|
|
706
|
+
<br><span style="color:var(--yellow)">⚠️ 每次自动跟进 = 一轮真实模型调用(花额度),故最多 5 次上限、完成即停。</span>
|
|
707
|
+
</div>
|
|
708
|
+
<label style="display:flex;align-items:center;gap:0.5rem;padding:0.45rem 0.2rem;font-size:0.86rem;cursor:pointer;border-top:1px solid var(--surface2)">
|
|
709
|
+
<input type="checkbox" id="fu-enabled" onchange="saveFollowUp()" style="width:1.15rem;height:1.15rem"> 开启自动跟进
|
|
710
|
+
</label>
|
|
711
|
+
<div style="display:flex;align-items:center;gap:0.5rem;padding:0.3rem 0.2rem;font-size:0.82rem">
|
|
712
|
+
跟进间隔:
|
|
713
|
+
<select id="fu-interval" class="slash-inp" onchange="saveFollowUp()" style="max-width:130px">
|
|
714
|
+
<option value="5">5 分钟</option>
|
|
715
|
+
<option value="10">10 分钟</option>
|
|
716
|
+
<option value="15">15 分钟</option>
|
|
717
|
+
<option value="30">30 分钟</option>
|
|
718
|
+
<option value="60">60 分钟</option>
|
|
719
|
+
</select>
|
|
720
|
+
</div>
|
|
721
|
+
<div id="fu-status" style="font-size:0.72rem;color:var(--muted);padding:0.3rem 0.2rem"></div>
|
|
722
|
+
<button class="action-sheet-cancel" onclick="closeFollowUpSheet()">关闭</button>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
|
|
679
726
|
<div id="memory-sheet" class="action-sheet">
|
|
680
727
|
<div class="action-sheet-backdrop" onclick="closeMemorySheet()"></div>
|
|
681
728
|
<div class="action-sheet-box" style="height:90vh;max-height:90vh;display:flex;flex-direction:column">
|
|
@@ -1117,14 +1164,107 @@
|
|
|
1117
1164
|
const TOOL_ICONS = { Read:'📄', Edit:'✏️', Write:'📝', Bash:'💻', Grep:'🔍', Glob:'📂', WebFetch:'🌐', WebSearch:'🔎', LSP:'🔧', Task:'🤖', Agent:'🤖' };
|
|
1118
1165
|
|
|
1119
1166
|
// 子代理(Task 工具)渲染:把 subagent_type / description / prompt 拆开展示,
|
|
1120
|
-
// 而不是甩一坨 input JSON。返回 { label, body }
|
|
1167
|
+
// 而不是甩一坨 input JSON。返回 { label, body, desc } 交给面板/工具卡。
|
|
1121
1168
|
function subagentToolInfo(input) {
|
|
1122
1169
|
const type = (input && input.subagent_type) || 'general-purpose';
|
|
1123
1170
|
const desc = (input && input.description) || '';
|
|
1124
1171
|
const prompt = (input && input.prompt) || '';
|
|
1125
1172
|
const label = `子代理 · ${type}`;
|
|
1126
1173
|
const body = (desc ? `📋 ${desc}\n\n` : '') + (prompt || JSON.stringify(input || {}, null, 2));
|
|
1127
|
-
return { label, body };
|
|
1174
|
+
return { label, body, desc };
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// ── 子代理(Task)面板:子代理内部活动带 parent_tool_use_id 冒泡到主 stream(见 agent.js),
|
|
1178
|
+
// 这里按 taskId(=Task 的 tool_use id) 归拢到一张可折叠面板 + 状态灯,主流不再被平铺污染 ──
|
|
1179
|
+
let _subagents = {}; // taskId → { el, bodyEl, dotEl, metaEl, toolCount, state }
|
|
1180
|
+
function subaResetAll() { _subagents = {}; }
|
|
1181
|
+
|
|
1182
|
+
// 生成一个可折叠的内部工具项(复用工具卡样式),放进子代理面板 body
|
|
1183
|
+
function subaMakeItem(name, payload) {
|
|
1184
|
+
const icon = TOOL_ICONS[name] || '🔧';
|
|
1185
|
+
const item = document.createElement('div'); item.className = 'msg-tool';
|
|
1186
|
+
const h = document.createElement('div'); h.className = 'tool-header';
|
|
1187
|
+
h.innerHTML = `<span class="arrow">▶</span> ${icon} ${escHtml(name || '工具')}`;
|
|
1188
|
+
const b = document.createElement('div'); b.className = 'tool-body';
|
|
1189
|
+
b.innerHTML = renderAnsiHtml(typeof payload === 'string' ? payload : JSON.stringify(payload || {}, null, 2));
|
|
1190
|
+
h.onclick = () => { b.classList.toggle('open'); h.querySelector('.arrow').classList.toggle('open'); };
|
|
1191
|
+
item.appendChild(h); item.appendChild(b);
|
|
1192
|
+
return item;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function subaCreatePanel(taskId, info, opts) {
|
|
1196
|
+
if (!taskId) return null;
|
|
1197
|
+
if (_subagents[taskId]) return _subagents[taskId];
|
|
1198
|
+
endStream(); removeThinking(); endToolGroup();
|
|
1199
|
+
const el = document.createElement('div'); el.className = 'msg msg-subagent';
|
|
1200
|
+
const header = document.createElement('div'); header.className = 'sub-header';
|
|
1201
|
+
const arrow = document.createElement('span'); arrow.className = 'arrow'; arrow.textContent = '▶';
|
|
1202
|
+
const dot = document.createElement('span'); dot.className = 'sub-dot running';
|
|
1203
|
+
const title = document.createElement('span'); title.innerHTML = `🤖 ${escHtml((info && info.label) || '子代理')}`;
|
|
1204
|
+
const meta = document.createElement('span'); meta.className = 'sub-meta'; meta.textContent = '运行中…';
|
|
1205
|
+
header.appendChild(arrow); header.appendChild(dot); header.appendChild(title); header.appendChild(meta);
|
|
1206
|
+
const bodyEl = document.createElement('div'); bodyEl.className = 'sub-body';
|
|
1207
|
+
if (info && info.desc) { const d = document.createElement('div'); d.className = 'sub-desc'; d.textContent = '📋 ' + info.desc; bodyEl.appendChild(d); }
|
|
1208
|
+
header.onclick = () => { bodyEl.classList.toggle('open'); arrow.classList.toggle('open'); };
|
|
1209
|
+
el.appendChild(header); el.appendChild(bodyEl);
|
|
1210
|
+
chatArea.appendChild(el); trimMessages();
|
|
1211
|
+
const rec = { el, bodyEl, dotEl: dot, metaEl: meta, toolCount: 0, state: 'running' };
|
|
1212
|
+
_subagents[taskId] = rec;
|
|
1213
|
+
if (opts && opts.done) subaMarkDone(taskId, null);
|
|
1214
|
+
scrollToBottom();
|
|
1215
|
+
return rec;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// 把一条带 parent_tool_use_id 的消息拆成活动,追加进对应面板。命中返回 true。
|
|
1219
|
+
function subaRoute(msg) {
|
|
1220
|
+
const pid = msg && msg.parent_tool_use_id;
|
|
1221
|
+
const rec = pid && _subagents[pid];
|
|
1222
|
+
if (!rec) return false;
|
|
1223
|
+
const blocks = msg.message && msg.message.content;
|
|
1224
|
+
if (Array.isArray(blocks)) {
|
|
1225
|
+
for (const bl of blocks) {
|
|
1226
|
+
if (bl.type === 'tool_use') {
|
|
1227
|
+
rec.bodyEl.appendChild(subaMakeItem(bl.name === 'Task' ? '子代理' : bl.name, bl.input));
|
|
1228
|
+
rec.toolCount++;
|
|
1229
|
+
} else if (bl.type === 'text' && bl.text && bl.text.trim()) {
|
|
1230
|
+
const t = document.createElement('div'); t.className = 'sub-act';
|
|
1231
|
+
t.innerHTML = `<span class="k">💬</span> ${escHtml(bl.text.trim())}`;
|
|
1232
|
+
rec.bodyEl.appendChild(t);
|
|
1233
|
+
} else if (bl.type === 'tool_result') {
|
|
1234
|
+
const c = typeof bl.content === 'string' ? bl.content
|
|
1235
|
+
: Array.isArray(bl.content) ? bl.content.map(x => x.text || '').join('\n') : JSON.stringify(bl.content);
|
|
1236
|
+
if (c && c.trim()) rec.bodyEl.appendChild(subaMakeItem('↳ 结果', c));
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
} else if (typeof msg.content === 'string' && msg.content.trim()) {
|
|
1240
|
+
const t = document.createElement('div'); t.className = 'sub-act';
|
|
1241
|
+
t.innerHTML = `<span class="k">↳</span> ${escHtml(msg.content.trim())}`;
|
|
1242
|
+
rec.bodyEl.appendChild(t);
|
|
1243
|
+
}
|
|
1244
|
+
if (rec.state === 'running') rec.metaEl.textContent = `运行中… ${rec.toolCount} 步`;
|
|
1245
|
+
scrollToBottom();
|
|
1246
|
+
return true;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function subaMarkDone(taskId, summary) {
|
|
1250
|
+
const rec = _subagents[taskId];
|
|
1251
|
+
if (!rec || rec.state === 'done') return;
|
|
1252
|
+
rec.state = 'done';
|
|
1253
|
+
rec.dotEl.className = 'sub-dot done';
|
|
1254
|
+
rec.metaEl.textContent = `✅ 完成 · ${rec.toolCount} 步`;
|
|
1255
|
+
if (summary && String(summary).trim()) {
|
|
1256
|
+
const s = document.createElement('div'); s.className = 'sub-summary';
|
|
1257
|
+
s.textContent = String(summary).trim();
|
|
1258
|
+
rec.bodyEl.appendChild(s);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
function subaMarkAllDone() { for (const id in _subagents) subaMarkDone(id, null); }
|
|
1262
|
+
|
|
1263
|
+
// 主线里 Task 的 tool_result(tool_use_id == taskId)→ 面板转完成 + 摘要。命中返回 true。
|
|
1264
|
+
function subaTryComplete(toolUseId, content) {
|
|
1265
|
+
if (!toolUseId || !_subagents[toolUseId]) return false;
|
|
1266
|
+
subaMarkDone(toolUseId, content);
|
|
1267
|
+
return true;
|
|
1128
1268
|
}
|
|
1129
1269
|
|
|
1130
1270
|
// ── 消息渲染 ──────────────────────────────────
|
|
@@ -1798,6 +1938,8 @@
|
|
|
1798
1938
|
// ── Agent 模式事件 ──────────────────────────────
|
|
1799
1939
|
socket.on('agent:message', (msg) => {
|
|
1800
1940
|
if (!msg) return;
|
|
1941
|
+
// 子代理内部活动(带 parent_tool_use_id)归入对应面板,不平铺进主流
|
|
1942
|
+
if (subaRoute(msg)) { setWorkState('working'); return; }
|
|
1801
1943
|
switch (msg.type) {
|
|
1802
1944
|
case 'system':
|
|
1803
1945
|
if (msg.subtype === 'init') {
|
|
@@ -1836,15 +1978,14 @@
|
|
|
1836
1978
|
if (block.name === 'AskUserQuestion' && block.input) {
|
|
1837
1979
|
openAskSheet(block.input);
|
|
1838
1980
|
}
|
|
1839
|
-
if (block.name === 'Task'
|
|
1840
|
-
|
|
1841
|
-
addToolMessage(info.body, info.label);
|
|
1981
|
+
if (block.name === 'Task') {
|
|
1982
|
+
subaCreatePanel(block.id, subagentToolInfo(block.input || {}));
|
|
1842
1983
|
} else {
|
|
1843
1984
|
addToolMessage(JSON.stringify(block.input || {}, null, 2), block.name || '工具');
|
|
1844
1985
|
}
|
|
1845
1986
|
} else if (block.type === 'tool_result') {
|
|
1846
1987
|
const content = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
|
|
1847
|
-
addRawOutput(content);
|
|
1988
|
+
if (!subaTryComplete(block.tool_use_id, content)) addRawOutput(content);
|
|
1848
1989
|
}
|
|
1849
1990
|
}
|
|
1850
1991
|
}
|
|
@@ -1860,6 +2001,7 @@
|
|
|
1860
2001
|
// 轮次结束时确保闹钟 chip 被扫描(防止最后一条消息未经过 endStream)
|
|
1861
2002
|
endStream();
|
|
1862
2003
|
const cost = msg.total_cost_usd ? ` ($${msg.total_cost_usd.toFixed(4)})` : '';
|
|
2004
|
+
subaMarkAllDone(); // 轮结束:仍在"运行中"的子代理面板一律收尾为完成(兜底)
|
|
1863
2005
|
addStatusMessage(`完成${cost}`);
|
|
1864
2006
|
// 关键:idle 放最后——渲染最终答复的 addAssistantMessage 会把状态置回 'working',
|
|
1865
2007
|
// 若在其之前置 idle 会被顶回「执行中」+ 取消按钮不复位。渲染全部完成后再回 idle。
|
|
@@ -1903,9 +2045,8 @@
|
|
|
1903
2045
|
if (msg.name === 'AskUserQuestion' && msg.input) {
|
|
1904
2046
|
openAskSheet(msg.input);
|
|
1905
2047
|
}
|
|
1906
|
-
if (msg.name === 'Task'
|
|
1907
|
-
|
|
1908
|
-
addToolMessage(info.body, info.label);
|
|
2048
|
+
if (msg.name === 'Task') {
|
|
2049
|
+
subaCreatePanel(msg.id, subagentToolInfo(msg.input || {}));
|
|
1909
2050
|
} else {
|
|
1910
2051
|
addToolMessage(JSON.stringify(msg.input || {}, null, 2), msg.name || '工具');
|
|
1911
2052
|
}
|
|
@@ -1919,7 +2060,7 @@
|
|
|
1919
2060
|
const text = typeof msg.content === 'string' ? msg.content
|
|
1920
2061
|
: Array.isArray(msg.content) ? msg.content.map(b => b.text || '').join('\n')
|
|
1921
2062
|
: JSON.stringify(msg.content);
|
|
1922
|
-
if (text.trim()) addRawOutput(text);
|
|
2063
|
+
if (text.trim() && !subaTryComplete(msg.tool_use_id, text)) addRawOutput(text);
|
|
1923
2064
|
}
|
|
1924
2065
|
setWorkState('working');
|
|
1925
2066
|
break;
|
|
@@ -1950,8 +2091,10 @@
|
|
|
1950
2091
|
_historyHasMore = true;
|
|
1951
2092
|
_userScrolledUp = false;
|
|
1952
2093
|
|
|
2094
|
+
subaResetAll();
|
|
1953
2095
|
for (const msg of history) {
|
|
1954
2096
|
endStream(); // 每条历史消息都是独立的,不要流式合并
|
|
2097
|
+
if (subaRoute(msg)) continue; // 子代理内部历史消息归入对应面板
|
|
1955
2098
|
if (msg.type === 'user' && msg.content) {
|
|
1956
2099
|
addInputMessage(msg.content);
|
|
1957
2100
|
} else if (msg.type === 'assistant' && msg.message?.content) {
|
|
@@ -1960,7 +2103,10 @@
|
|
|
1960
2103
|
for (const block of msg.message.content) {
|
|
1961
2104
|
if (block.type === 'thinking' && block.thinking) { showThinking(block.thinking); removeThinking(); }
|
|
1962
2105
|
else if (block.type === 'text' && block.text) texts.push(block.text);
|
|
1963
|
-
else if (block.type === 'tool_use')
|
|
2106
|
+
else if (block.type === 'tool_use') {
|
|
2107
|
+
if (block.name === 'Task') subaCreatePanel(block.id, subagentToolInfo(block.input || {}), { done: false });
|
|
2108
|
+
else addToolMessage(JSON.stringify(block.input || {}, null, 2), block.name || '工具');
|
|
2109
|
+
}
|
|
1964
2110
|
}
|
|
1965
2111
|
if (texts.length) {
|
|
1966
2112
|
const combined = texts.join('\n\n');
|
|
@@ -1977,6 +2123,7 @@
|
|
|
1977
2123
|
addStatusMessage(`完成${cost}`);
|
|
1978
2124
|
}
|
|
1979
2125
|
}
|
|
2126
|
+
subaMarkAllDone(); // 历史都是过去式:剩余"运行中"的子代理面板收尾为完成
|
|
1980
2127
|
|
|
1981
2128
|
// 还原断点处的流式气泡,让后续 live delta 接着拼
|
|
1982
2129
|
if (inflightText) {
|
|
@@ -3188,6 +3335,42 @@
|
|
|
3188
3335
|
} catch {}
|
|
3189
3336
|
};
|
|
3190
3337
|
|
|
3338
|
+
// ── 自动跟进(会话级):长任务完成后自动汇报进度 ──────────────
|
|
3339
|
+
window.openFollowUpSheet = function() {
|
|
3340
|
+
document.getElementById('followup-sheet').classList.add('open');
|
|
3341
|
+
loadFollowUp();
|
|
3342
|
+
};
|
|
3343
|
+
window.closeFollowUpSheet = function() {
|
|
3344
|
+
document.getElementById('followup-sheet').classList.remove('open');
|
|
3345
|
+
};
|
|
3346
|
+
function _fuStatusText(d) {
|
|
3347
|
+
return d.enabled ? `已开启 · 每 ${d.intervalMinutes} 分钟 · 已跟进 ${d.count}/${d.max} 次` : '未开启';
|
|
3348
|
+
}
|
|
3349
|
+
async function loadFollowUp() {
|
|
3350
|
+
try {
|
|
3351
|
+
const r = await fetch(`/api/session/${SESSION_ID}/followup`);
|
|
3352
|
+
if (!r.ok) { document.getElementById('fu-status').textContent = '(此会话不支持自动跟进)'; return; }
|
|
3353
|
+
const d = await r.json();
|
|
3354
|
+
document.getElementById('fu-enabled').checked = !!d.enabled;
|
|
3355
|
+
document.getElementById('fu-interval').value = String(d.intervalMinutes || 15);
|
|
3356
|
+
document.getElementById('fu-status').textContent = _fuStatusText(d);
|
|
3357
|
+
} catch { document.getElementById('fu-status').textContent = '连接失败'; }
|
|
3358
|
+
}
|
|
3359
|
+
window.saveFollowUp = async function() {
|
|
3360
|
+
const enabled = document.getElementById('fu-enabled').checked;
|
|
3361
|
+
const intervalMinutes = parseInt(document.getElementById('fu-interval').value, 10) || 15;
|
|
3362
|
+
try {
|
|
3363
|
+
const r = await fetch(`/api/session/${SESSION_ID}/followup`, {
|
|
3364
|
+
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
3365
|
+
body: JSON.stringify({ enabled, intervalMinutes }),
|
|
3366
|
+
});
|
|
3367
|
+
const d = await r.json();
|
|
3368
|
+
if (d.error) { addStatusMessage(d.error); return; }
|
|
3369
|
+
document.getElementById('fu-status').textContent = _fuStatusText(d);
|
|
3370
|
+
addStatusMessage(enabled ? `🔄 自动跟进已开启(每 ${intervalMinutes} 分钟)` : '🔄 自动跟进已关闭');
|
|
3371
|
+
} catch { addStatusMessage('设置失败'); }
|
|
3372
|
+
};
|
|
3373
|
+
|
|
3191
3374
|
// ── 隐私护栏(Layer C,去标识化):会话级自助维护 ──────────────
|
|
3192
3375
|
window.openGuardSheet = function() {
|
|
3193
3376
|
document.getElementById('guard-sheet').classList.add('open');
|