@wendongfly/myhi 1.3.206 → 1.3.208
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 +43 -10
- package/dist/chat.html.gz +0 -0
- package/dist/index.html +21 -10
- package/dist/index.html.gz +0 -0
- package/dist/index.js +1 -1
- package/dist/index.min.js +173 -162
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -1021,7 +1021,6 @@
|
|
|
1021
1021
|
<div id="model-pop">
|
|
1022
1022
|
<div id="model-pop-list"></div>
|
|
1023
1023
|
<div class="mp-foot">
|
|
1024
|
-
<input id="model-pop-custom" placeholder="其他模型:输入名字后回车" autocomplete="off" spellcheck="false" onkeydown="if(event.key==='Enter'&&this.value.trim()){closeModelPop();selectModel({id:this.value.trim(),label:this.value.trim()});this.value=''}">
|
|
1025
1024
|
<a id="model-pop-cfg" href="/?settings=1" target="_top">⚙ 配置模型</a>
|
|
1026
1025
|
</div>
|
|
1027
1026
|
</div>
|
|
@@ -2573,7 +2572,22 @@
|
|
|
2573
2572
|
return null;
|
|
2574
2573
|
}
|
|
2575
2574
|
|
|
2575
|
+
// 看图转文字的结果:折叠块,点开能看到主模型实际拿到的图片描述(描述可能漏细节,给用户核对)
|
|
2576
|
+
function visionEl(msg) {
|
|
2577
|
+
const d = document.createElement('details');
|
|
2578
|
+
d.className = 'msg msg-vision';
|
|
2579
|
+
d.style.cssText = 'margin:2px auto 6px;max-width:92%;font-size:0.8rem;color:var(--muted);border:1px solid var(--border);border-radius:8px;padding:4px 10px';
|
|
2580
|
+
d.innerHTML = `<summary style="cursor:pointer">🖼 看图模型 ${escHtml(msg.vision.model || '')} 转成的文字(点开核对)</summary>` +
|
|
2581
|
+
(msg.vision.items || []).map(it => `<div style="margin:6px 0 2px;word-break:break-all">${escHtml(it.path || '')}</div>` +
|
|
2582
|
+
`<pre style="white-space:pre-wrap;word-break:break-word;margin:0;font-size:0.78rem;color:var(--text)">${escHtml(it.text || ('识别失败:' + (it.error || '')))}</pre>`).join('');
|
|
2583
|
+
return d;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2576
2586
|
function renderHistoryMessage(msg, parent) {
|
|
2587
|
+
if (msg.type === 'system' && msg.vision) {
|
|
2588
|
+
const st = document.createElement('div'); st.className = 'msg msg-status'; st.textContent = '— ' + msg.message + ' —';
|
|
2589
|
+
parent.appendChild(st); parent.appendChild(visionEl(msg)); return;
|
|
2590
|
+
}
|
|
2577
2591
|
if (msg.type === 'assistant' && Array.isArray(msg.message?.content)) {
|
|
2578
2592
|
for (const b of msg.message.content) { const k = blockKey(b, msg.message.id); if (k) _replayedBlocks.add(k); }
|
|
2579
2593
|
} else if (msg.type === 'tool_result' && msg.id) _replayedBlocks.add('tr:' + msg.id);
|
|
@@ -2606,11 +2620,21 @@
|
|
|
2606
2620
|
const sel = buildSyntheticUserEl(msg.content);
|
|
2607
2621
|
if (sel) { parent.appendChild(sel); return; }
|
|
2608
2622
|
if (syntheticUserKind(msg.content)) return;
|
|
2623
|
+
// 看图转文字后发给模型的消息(对话记录里就是改写后的全文):气泡只显示原话,图片描述收进折叠块
|
|
2624
|
+
let content = msg.content, vis = null;
|
|
2625
|
+
const vi = typeof content === 'string' ? content.indexOf('\n\n(注意:你当前用的模型不能直接看图') : -1;
|
|
2626
|
+
if (vi > 0) {
|
|
2627
|
+
const tail = content.slice(vi);
|
|
2628
|
+
vis = { message: '', vision: { model: (tail.match(/看图模型 (\S+) 把图片/) || [])[1] || '',
|
|
2629
|
+
items: [...tail.matchAll(/【图片 ([^】]+)】\n([\s\S]*?)(?=\n\n【图片 |$)/g)].map(m => ({ path: m[1], text: m[2] })) } };
|
|
2630
|
+
content = content.slice(0, vi);
|
|
2631
|
+
}
|
|
2609
2632
|
const el = document.createElement('div');
|
|
2610
2633
|
el.className = 'msg msg-input' + (msg.inject ? ' msg-inject' : '');
|
|
2611
2634
|
const full = msg.timestamp ? new Date(msg.timestamp).toLocaleString('zh-CN') : '';
|
|
2612
|
-
el.innerHTML = `<div><div class="bubble">${escHtml(
|
|
2635
|
+
el.innerHTML = `<div><div class="bubble">${escHtml(content)}</div><div class="meta" title="${escHtml(full)}">${injectTagHtml(msg.inject)}${msg.timestamp ? formatTime(new Date(msg.timestamp)) : ''}</div></div>`;
|
|
2613
2636
|
parent.appendChild(el);
|
|
2637
|
+
if (vis) parent.appendChild(visionEl(vis));
|
|
2614
2638
|
return;
|
|
2615
2639
|
}
|
|
2616
2640
|
|
|
@@ -3150,7 +3174,8 @@
|
|
|
3150
3174
|
updateControlUI(session.controlHolder === socket.id, session.controlHolderName);
|
|
3151
3175
|
|
|
3152
3176
|
claudeSession = isClaudeSession(session) || session.mode === 'agent';
|
|
3153
|
-
|
|
3177
|
+
// 模型按钮只在配了自定义网关时出现(列表来自网关),由 refreshModelChip 决定显隐
|
|
3178
|
+
if (session.mode !== 'agent') document.getElementById('model-chip').style.display = 'none';
|
|
3154
3179
|
if (session.mode === 'agent') refreshModelChip();
|
|
3155
3180
|
updateShortcutBar();
|
|
3156
3181
|
updatePlanButton();
|
|
@@ -3221,6 +3246,7 @@
|
|
|
3221
3246
|
_ctxNudgeLevel = 0; _lastCtxTokens = 0; ctxUpdate(0);
|
|
3222
3247
|
}
|
|
3223
3248
|
addStatusMessage(msg.message);
|
|
3249
|
+
if (msg.vision) { chatArea.appendChild(visionEl(msg)); scrollToBottom(); }
|
|
3224
3250
|
// 同步计划模式状态到按钮
|
|
3225
3251
|
if (msg.message.includes('进入计划模式')) {
|
|
3226
3252
|
if (currentSession) currentSession.permissionMode = 'plan';
|
|
@@ -5630,10 +5656,14 @@
|
|
|
5630
5656
|
await loadModelInfo();
|
|
5631
5657
|
const cur = currentSession?.modelId || '';
|
|
5632
5658
|
const main = _modelInfo?.scenes?.main || '';
|
|
5659
|
+
// 计划模式且系统设置里配了「计划」模型 → 这段时间实际用它(批准执行后回到主任务模型)
|
|
5660
|
+
const plan = currentSession?.permissionMode === 'plan' ? (_modelInfo?.scenes?.plan || '') : '';
|
|
5633
5661
|
const el = document.getElementById('model-chip-name');
|
|
5634
|
-
el.textContent = cur ? modelName(cur) : main ? modelName(main) : '默认';
|
|
5662
|
+
el.textContent = plan ? '计划·' + modelName(plan) : cur ? modelName(cur) : main ? modelName(main) : '默认';
|
|
5635
5663
|
const chip = document.getElementById('model-chip'), q = quotaState();
|
|
5636
|
-
|
|
5664
|
+
// 没配自定义网关(直连 Anthropic 等):不提供模型选择,跟随 Claude Code 默认
|
|
5665
|
+
chip.style.display = currentSession?.mode === 'agent' && _modelInfo?.gateway ? '' : 'none';
|
|
5666
|
+
chip.title = '切换模型 · 当前:' + (plan ? plan + '(计划模式,系统设置 · 计划)\n执行时:' : '') + (cur || (main ? main + '(主任务默认)' : 'Claude Code 默认')) + (q.text ? '\n' + (q.out ? q.msg : q.text) : '');
|
|
5637
5667
|
chip.style.borderColor = q.out ? 'var(--red)' : ''; // 积分用尽:按钮描红
|
|
5638
5668
|
};
|
|
5639
5669
|
window.openModelPop = async function() {
|
|
@@ -5648,7 +5678,7 @@
|
|
|
5648
5678
|
const row = (id, name, sub, right, tags, on, unfit, tip) => `<div class="mp-i${on ? ' on' : ''}" data-model="${escAttr(id)}" data-label="${escAttr(name)}"${unfit ? ` data-unfit="${escAttr(unfit)}" style="opacity:0.55"` : ''}${tip ? ` title="${escAttr(tip)}"` : ''}><span class="mp-n">${escHtml(name)}${unfit ? `<span class="mp-sub" style="color:var(--yellow)">⚠ ${escHtml(unfit)}</span>` : sub ? `<span class="mp-sub">${escHtml(sub)}</span>` : ''}</span>${(tags || []).map(t => TAG_LABEL[t] ? `<span class="mp-tag" style="color:${TAG_LABEL[t][1]};border-color:${TAG_LABEL[t][1]}">${TAG_LABEL[t][0]}</span>` : '').join('')}${right ? `<span class="mp-r">${escHtml(right)}</span>` : ''}${on ? '<span class="mp-ck">✓</span>' : ''}</div>`;
|
|
5649
5679
|
const q = quotaState();
|
|
5650
5680
|
let html = q.text ? `<div class="mp-h" style="${q.out ? 'color:var(--red);font-weight:600' : ''}">${escHtml(q.out ? q.msg : q.text)}</div>` : '';
|
|
5651
|
-
html += row('default', '默认', main ? '
|
|
5681
|
+
html += row('default', '默认', main ? '主任务默认:' + main : '跟随 Claude Code 默认', '', [], !cur);
|
|
5652
5682
|
const gm = (d.models || []).filter(m => !m.id.includes('*')); // 通配别名(claude-*)不能直接选
|
|
5653
5683
|
const listed = new Set(['default']);
|
|
5654
5684
|
if (gm.length) {
|
|
@@ -5661,16 +5691,18 @@
|
|
|
5661
5691
|
const sub = [m.kind === 'alias' && m.target ? '→ ' + m.target : '', m.caps.map(c => CAP_ICON[c]).filter(Boolean).join(' ')].filter(Boolean).join(' · ');
|
|
5662
5692
|
// 右侧:有渠道系数就显示倍率(如 0.51x),否则显示单价;单价放悬停提示里
|
|
5663
5693
|
const price = m.inputCost != null ? `$${m.inputCost}/$${m.outputCost} 每百万 token` : '';
|
|
5664
|
-
|
|
5665
|
-
|
|
5694
|
+
// 时段价此刻生效(如「闲时 0.5x」)→ 放在右侧倍率前面
|
|
5695
|
+
const tn = m.timeNow ? `${m.timeNow.label}${m.timeNow.multiplier != null ? ' ' + m.timeNow.multiplier + 'x' : ''}` : '';
|
|
5696
|
+
const right = [tn, m.multiplier != null ? `${m.multiplier}x` : (m.inputCost != null ? `$${m.inputCost}/$${m.outputCost}` : '')].filter(Boolean).join(' · ');
|
|
5697
|
+
html += row(m.id, m.kind === 'alias' ? m.id : m.name, sub, right, m.tags, cur === m.id, m.fit?.main || '', [price, m.multiplier != null ? `渠道系数 ${m.multiplier}` : '', tn ? '当前时段:' + tn : ''].filter(Boolean).join(' · '));
|
|
5666
5698
|
}
|
|
5667
5699
|
}
|
|
5668
5700
|
} else if (d.gateway && d.error) html += `<div class="mp-h">网关模型列表读取失败:${escHtml(d.error)}</div>`;
|
|
5669
5701
|
// 自定义:本地场景配置里填过、或本会话正在用、但不在网关列表里的名字
|
|
5670
5702
|
const custom = [...new Set([cur, ...Object.values(d.scenes || {})].filter(x => x && !listed.has(x)))];
|
|
5671
5703
|
if (custom.length) { html += '<div class="mp-h">自定义模型</div>'; for (const id of custom) { listed.add(id); html += row(id, id, '', '', [], cur === id); } }
|
|
5672
|
-
|
|
5673
|
-
|
|
5704
|
+
// 模型只从网关目录来;没配自定义网关就不提供选择
|
|
5705
|
+
if (!d.gateway) html = '<div class="mp-h" style="white-space:normal;line-height:1.5">没有配置自定义网关,使用 Claude Code 默认模型。<br>配置网关后,这里会列出网关上这个 token 可用的模型。</div>';
|
|
5674
5706
|
list.innerHTML = html;
|
|
5675
5707
|
list.querySelectorAll('[data-model]').forEach(el => el.onclick = () => {
|
|
5676
5708
|
if (el.dataset.unfit && !confirm(`「${el.dataset.label}」${el.dataset.unfit}。\n\n用它当主会话模型,AI 很可能干不了活。仍要切换吗?`)) return;
|
|
@@ -6631,6 +6663,7 @@
|
|
|
6631
6663
|
btn.textContent = isPlan ? '退出计划' : '计划';
|
|
6632
6664
|
btn.style.color = isPlan ? 'var(--orange)' : '';
|
|
6633
6665
|
btn.style.fontWeight = isPlan ? '600' : '';
|
|
6666
|
+
if (currentSession?.mode === 'agent' && window.refreshModelChip) refreshModelChip(); // 计划模型可能不同
|
|
6634
6667
|
}
|
|
6635
6668
|
window.togglePlanMode = function() {
|
|
6636
6669
|
const isPlan = currentSession?.permissionMode === 'plan';
|
package/dist/chat.html.gz
CHANGED
|
Binary file
|
package/dist/index.html
CHANGED
|
@@ -958,14 +958,14 @@
|
|
|
958
958
|
|
|
959
959
|
<!-- 场景模型:同一个网关 + token,不同场景用不同模型(仅管理员)-->
|
|
960
960
|
<div id="set-models" class="admin-only">
|
|
961
|
-
<div class="set-group-t"
|
|
961
|
+
<div class="set-group-t">系统设置 · 模型(所有任务生效)</div>
|
|
962
962
|
<div class="set-card">
|
|
963
963
|
<div class="set-item" style="display:block;font-size:0.74rem;color:var(--muted);line-height:1.6" id="set-models-src">读取网关模型列表…</div>
|
|
964
964
|
<div id="set-models-rows"></div>
|
|
965
965
|
<div class="set-item" style="justify-content:flex-end"><button class="set-btn" id="set-models-save" onclick="saveSceneModels()">保存</button></div>
|
|
966
966
|
</div>
|
|
967
967
|
<div id="set-models-status" class="set-status"></div>
|
|
968
|
-
<datalist id="set-models-dl-tools"></datalist>
|
|
968
|
+
<datalist id="set-models-dl-tools"></datalist><datalist id="set-models-dl-vision"></datalist>
|
|
969
969
|
<datalist id="set-models-dl-all"></datalist>
|
|
970
970
|
</div>
|
|
971
971
|
|
|
@@ -1497,11 +1497,13 @@
|
|
|
1497
1497
|
// ── 场景模型 ────────────────────────────────────────────
|
|
1498
1498
|
// 每个场景一个输入框:可从网关模型列表里选(datalist),也可手填任意模型名 / 别名;留空 = 用默认
|
|
1499
1499
|
const SCENE_HINT = {
|
|
1500
|
-
main: '
|
|
1501
|
-
|
|
1500
|
+
main: '任务对话默认用它(聊天框里没单独选模型时);留空 = Claude Code 默认',
|
|
1501
|
+
plan: '计划模式(只调研、出方案)用,可选更强的;批准执行后回到主任务模型;留空 = 跟主任务一样',
|
|
1502
|
+
subagent: 'AI 把任务拆成子任务时用;留空 = 跟主任务一样',
|
|
1502
1503
|
background: '生成标题、摘要等后台小活,建议选便宜的',
|
|
1503
1504
|
ask: '「问一问」和解释,建议选便宜快速的;留空 = sonnet',
|
|
1504
1505
|
voice: '语音转成书面文字,建议选便宜的;留空 = 同问一问',
|
|
1506
|
+
vision: '主任务模型看不了图时,先用它把聊天框发的图片转成文字再交给主模型(多花几秒,可在聊天里点开核对);留空 = 不转',
|
|
1505
1507
|
};
|
|
1506
1508
|
async function loadSceneModels() {
|
|
1507
1509
|
let d; try { d = await (await fetch('/api/models')).json(); } catch { return; }
|
|
@@ -1510,26 +1512,35 @@
|
|
|
1510
1512
|
const ms = (d.models || []).filter(m => !m.id.includes('*'));
|
|
1511
1513
|
const t = d.token, quota = t && (t.dailyLimit != null || t.monthlyLimit != null)
|
|
1512
1514
|
? ` 积分:${t.dailyLimit != null ? `今日已用 ${t.dailyUsed ?? '?'} / ${t.dailyLimit}` : ''}${t.monthlyLimit != null ? ` 本月已用 ${t.monthlyUsed ?? '?'} / ${t.monthlyLimit}` : ''}。` : '';
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
+
// 没配自定义网关:不提供模型选择(全部跟随 Claude Code 默认),整组只留一句说明
|
|
1516
|
+
const noGw = !d.gateway;
|
|
1517
|
+
document.getElementById('set-models-rows').style.display = noGw ? 'none' : '';
|
|
1518
|
+
document.getElementById('set-models-save').parentElement.style.display = noGw ? 'none' : '';
|
|
1519
|
+
if (noGw) { src.textContent = '没有配置自定义网关:所有任务使用 Claude Code 默认模型,不需要选模型。配置网关后,可在这里按场景(主任务 / 计划 / 问一问 / 看图…)从网关模型里选。'; return; }
|
|
1520
|
+
src.textContent = d.error ? '网关模型列表读取失败(' + d.error + '),可以手填模型名。'
|
|
1515
1521
|
: `网关上有 ${ms.length} 个模型${d.scoped ? '(这个 token 可用的)' : '(网关是旧版,未按 token 过滤,个别模型可能用不了)'},点输入框可选,也可以手填。` + quota;
|
|
1516
1522
|
// 两份候选:主会话 / 子代理只列能用工具干活的;问一问、语音整理、后台小任务全列,便宜的排前面
|
|
1517
1523
|
const opt = (m) => `<option value="${esc(m.id)}">${esc([m.name !== m.id ? m.name : '', m.kind === 'alias' && m.target ? '→ ' + m.target : '', (m.tags || []).includes('cheap') ? '便宜' : '', m.multiplier != null ? m.multiplier + 'x' : '', (m.caps || []).includes('vision') ? '能看图' : ''].filter(Boolean).join(' · '))}</option>`;
|
|
1518
|
-
const aliases =
|
|
1524
|
+
const aliases = ''; // 模型只从网关目录选(不再列 Claude 别名)
|
|
1519
1525
|
const cheapFirst = [...ms].sort((a, b) => ((b.tags || []).includes('cheap') - (a.tags || []).includes('cheap')));
|
|
1520
1526
|
document.getElementById('set-models-dl-tools').innerHTML = ms.filter(m => !m.fit?.main).map(opt).join('') + aliases;
|
|
1521
1527
|
document.getElementById('set-models-dl-all').innerHTML = cheapFirst.map(opt).join('') + aliases;
|
|
1522
|
-
|
|
1528
|
+
document.getElementById('set-models-dl-vision').innerHTML = cheapFirst.filter(m => (m.caps || []).includes('vision')).map(opt).join('') + aliases;
|
|
1529
|
+
const TOOL_SCENES = { main: 1, plan: 1, subagent: 1, vision: 1 };
|
|
1530
|
+
const DL = (k) => k === 'vision' ? 'set-models-dl-vision' : TOOL_SCENES[k] ? 'set-models-dl-tools' : 'set-models-dl-all';
|
|
1523
1531
|
document.getElementById('set-models-rows').innerHTML = Object.entries(d.sceneLabels || {}).map(([k, label]) =>
|
|
1524
1532
|
`<div class="set-item inp"><div class="set-lbl">${esc(label)}<small>${esc(SCENE_HINT[k] || '')}</small></div>
|
|
1525
|
-
<div class="set-ctl"><input class="set-inp" data-scene="${esc(k)}" list="${
|
|
1533
|
+
<div class="set-ctl"><input class="set-inp" data-scene="${esc(k)}" list="${DL(k)}" value="${esc(d.scenes?.[k] || '')}" placeholder="默认" autocomplete="off" spellcheck="false"></div>
|
|
1526
1534
|
<div class="set-warn" data-warn="${esc(k)}" style="display:none;font-size:0.72rem;color:var(--yellow,#d29922)"></div></div>`).join('');
|
|
1527
1535
|
// 填了不适合这个场景的模型(不支持工具等)→ 输入框下提示原因(不硬拦:网关元数据可能没填全)
|
|
1528
1536
|
const byId = Object.fromEntries(ms.map(m => [m.id, m]));
|
|
1529
1537
|
const check = (inp) => {
|
|
1530
1538
|
const k = inp.dataset.scene, w = document.querySelector(`[data-warn="${k}"]`);
|
|
1531
1539
|
const why = TOOL_SCENES[k] && byId[inp.value.trim()]?.fit?.[k];
|
|
1532
|
-
|
|
1540
|
+
// 看图:主任务模型本身看不了图却没配看图模型 → 提醒(图片发过去 AI 会读不出来)
|
|
1541
|
+
const mainM = byId[(document.querySelector('[data-scene="main"]')?.value || '').trim()];
|
|
1542
|
+
const hint = k === 'vision' && !inp.value.trim() && mainM?.caps?.length && !mainM.caps.includes('vision') ? `主任务模型 ${mainM.name} 看不了图,建议配一个,否则发图片 AI 会读不出来` : '';
|
|
1543
|
+
w.textContent = why ? '⚠ ' + why : hint ? '⚠ ' + hint : ''; w.style.display = why || hint ? '' : 'none';
|
|
1533
1544
|
};
|
|
1534
1545
|
document.querySelectorAll('#set-models-rows [data-scene]').forEach(i => { check(i); i.addEventListener('input', () => check(i)); });
|
|
1535
1546
|
// 和其他设置项一样:改了才出现「保存」
|
package/dist/index.html.gz
CHANGED
|
Binary file
|