@wendongfly/myhi 1.3.205 → 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 +305 -26
- package/dist/chat.html.gz +0 -0
- package/dist/index.html +80 -4
- package/dist/index.html.gz +0 -0
- package/dist/index.js +1 -1
- package/dist/index.min.js +180 -169
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -324,6 +324,28 @@
|
|
|
324
324
|
@media (pointer: coarse) { #cmd-input { font-size: 1rem; padding: 0.75rem 0.75rem 0.35rem; line-height: 1.55; } }
|
|
325
325
|
#input-toolbar { display: flex; align-items: center; padding: 0.25rem 0.4rem 0.4rem; gap: 0.15rem; }
|
|
326
326
|
#input-toolbar .spacer { flex: 1; }
|
|
327
|
+
#model-chip { display: inline-flex; align-items: center; gap: 0.25rem; height: 28px; max-width: 11rem; padding: 0 0.55rem; margin-right: 0.15rem; border-radius: 14px; border: 1px solid var(--border); background: var(--surface2); color: var(--text2, var(--text)); font-size: 0.74rem; cursor: pointer; flex-shrink: 1; min-width: 0; }
|
|
328
|
+
#model-chip:hover { border-color: var(--purple); }
|
|
329
|
+
#model-chip svg { width: 14px; height: 14px; flex-shrink: 0; }
|
|
330
|
+
#model-chip #model-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
331
|
+
#model-chip .mc-caret { font-size: 0.7rem; color: var(--muted); flex-shrink: 0; }
|
|
332
|
+
@media (max-width: 480px) { #model-chip { max-width: 7.5rem; } }
|
|
333
|
+
#model-pop-ov { position: fixed; inset: 0; z-index: 80; display: none; }
|
|
334
|
+
#model-pop { position: fixed; z-index: 81; display: none; flex-direction: column; width: min(92vw, 340px); max-height: min(60vh, 460px); background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
|
|
335
|
+
#model-pop.open, #model-pop-ov.open { display: flex; }
|
|
336
|
+
#model-pop-list { overflow-y: auto; padding: 0.3rem 0; flex: 1; min-height: 0; }
|
|
337
|
+
#model-pop .mp-h { padding: 0.45rem 0.85rem 0.15rem; font-size: 0.68rem; color: var(--muted); }
|
|
338
|
+
#model-pop .mp-i { display: flex; align-items: center; gap: 0.45rem; padding: 0.45rem 0.85rem; cursor: pointer; font-size: 0.82rem; color: var(--text); }
|
|
339
|
+
#model-pop .mp-i:hover { background: var(--surface2); }
|
|
340
|
+
#model-pop .mp-i.on { background: var(--surface2); font-weight: 600; }
|
|
341
|
+
#model-pop .mp-n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
342
|
+
#model-pop .mp-sub { display: block; font-size: 0.66rem; color: var(--muted); font-weight: 400; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
343
|
+
#model-pop .mp-tag { font-size: 0.62rem; padding: 0 0.35rem; border-radius: 8px; border: 1px solid; white-space: nowrap; flex-shrink: 0; font-weight: 400; }
|
|
344
|
+
#model-pop .mp-r { font-size: 0.68rem; color: var(--muted); white-space: nowrap; flex-shrink: 0; }
|
|
345
|
+
#model-pop .mp-ck { color: var(--green); flex-shrink: 0; }
|
|
346
|
+
#model-pop .mp-foot { border-top: 1px solid var(--surface2); padding: 0.45rem 0.6rem; display: flex; gap: 0.5rem; align-items: center; }
|
|
347
|
+
#model-pop .mp-foot input { flex: 1; min-width: 0; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 7px; font-size: 0.74rem; padding: 0.3rem 0.5rem; outline: none; }
|
|
348
|
+
#model-pop .mp-foot a { font-size: 0.74rem; color: var(--text2, var(--text)); text-decoration: none; white-space: nowrap; }
|
|
327
349
|
.inj-btn { flex-shrink: 0; height: 30px; border: 1px solid var(--yellow); background: rgba(210,153,34,0.12); color: var(--yellow); border-radius: 8px; padding: 0 0.6rem; font-size: 0.76rem; font-weight: 600; cursor: pointer; white-space: nowrap; align-self: center; }
|
|
328
350
|
.inj-btn:hover { background: rgba(210,153,34,0.22); }
|
|
329
351
|
.inj-tag { font-size: 0.66rem; margin-right: 0.35rem; color: var(--yellow); }
|
|
@@ -417,6 +439,24 @@
|
|
|
417
439
|
#fp-sel-bar .fp-btn { padding: 0.3rem 0.45rem; white-space: nowrap; }
|
|
418
440
|
.fp-item .fp-more { color: var(--muted); padding: 0 0.45rem; font-size: 1rem; line-height: 1; letter-spacing: 1px; flex-shrink: 0; align-self: flex-start; border-radius: 6px; cursor: pointer; }
|
|
419
441
|
.fp-item .fp-more:hover { background: var(--surface2); color: var(--text); }
|
|
442
|
+
#fp-viewer { position: fixed; inset: 0; z-index: 95; background: rgba(0,0,0,0.92); display: none; flex-direction: column; }
|
|
443
|
+
#fp-viewer.open { display: flex; }
|
|
444
|
+
#fp-viewer .fv-head { display: flex; align-items: center; gap: 0.4rem; padding: 0.55rem 0.75rem; padding-top: max(0.55rem, env(safe-area-inset-top)); color: #eee; flex-shrink: 0; }
|
|
445
|
+
#fp-viewer .fv-title { flex: 1; min-width: 0; display: flex; flex-direction: column; font-size: 0.85rem; }
|
|
446
|
+
#fp-viewer .fv-title b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
|
|
447
|
+
#fp-viewer .fv-title span { font-size: 0.68rem; color: #aaa; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 'SF Mono', Consolas, monospace; }
|
|
448
|
+
#fp-viewer .fp-btn { background: rgba(255,255,255,0.12); color: #fff; border-color: rgba(255,255,255,0.2); white-space: nowrap; }
|
|
449
|
+
#fp-viewer .fv-body { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: auto; padding: 0 0.5rem max(0.5rem, env(safe-area-inset-bottom)); }
|
|
450
|
+
#fp-viewer .fv-body img { max-width: 100%; max-height: 100%; object-fit: contain; cursor: zoom-in; background: repeating-conic-gradient(#ccc 0 25%, #fff 0 50%) 0 0 / 16px 16px; }
|
|
451
|
+
#fp-viewer .fv-body img.zoom { max-width: none; max-height: none; cursor: zoom-out; }
|
|
452
|
+
#fp-viewer .fv-body.zoomed { align-items: flex-start; justify-content: flex-start; }
|
|
453
|
+
#fp-viewer .fv-body video { max-width: 100%; max-height: 100%; }
|
|
454
|
+
#fp-viewer .fv-body audio { width: min(90%, 32rem); }
|
|
455
|
+
#fp-viewer .fv-body iframe { width: 100%; height: 100%; border: 0; background: #fff; border-radius: 6px; }
|
|
456
|
+
#fp-viewer .fv-msg { color: #ddd; text-align: center; font-size: 0.88rem; line-height: 1.7; }
|
|
457
|
+
#fp-viewer .fv-nav { position: absolute; top: 50%; transform: translateY(-50%); width: 2.6rem; height: 3.6rem; border: none; border-radius: 8px; background: rgba(255,255,255,0.14); color: #fff; font-size: 1.8rem; cursor: pointer; display: none; }
|
|
458
|
+
#fp-viewer .fv-nav.prev { left: 0.5rem; } #fp-viewer .fv-nav.next { right: 0.5rem; }
|
|
459
|
+
#fp-viewer.multi .fv-nav { display: block; }
|
|
420
460
|
/* 行 / 工具 ⋯ 菜单:电脑上是贴着鼠标的小菜单,手机上是底部抽屉 */
|
|
421
461
|
#fp-menu-ov { position: fixed; inset: 0; z-index: 90; }
|
|
422
462
|
#fp-menu { position: fixed; z-index: 91; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.3); padding: 0.3rem 0; min-width: 12rem; max-width: 18rem; }
|
|
@@ -782,6 +822,11 @@
|
|
|
782
822
|
<input type="file" id="file-input" style="display:none" onchange="uploadTempFile(this.files[0]); this.value='';">
|
|
783
823
|
<span id="enter-hint" style="display:none;font-size:0.68rem;color:var(--muted);white-space:nowrap;align-self:center;margin-left:0.15rem"></span>
|
|
784
824
|
<div class="spacer"></div>
|
|
825
|
+
<!-- 模型:当前会话用的模型,点开在上方选(读 myhi 本地场景配置 + 网关模型目录) -->
|
|
826
|
+
<button id="model-chip" style="display:none" onclick="openModelPop()" title="切换模型">
|
|
827
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>
|
|
828
|
+
<span id="model-chip-name">默认</span><span class="mc-caret">⌃</span>
|
|
829
|
+
</button>
|
|
785
830
|
<button class="tb-btn" id="voice-btn" onclick="openVaSheet()" title="语音输入 / 需求助手">
|
|
786
831
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z"/><path d="M19 10v2a7 7 0 01-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
|
|
787
832
|
</button>
|
|
@@ -824,7 +869,6 @@
|
|
|
824
869
|
<div id="sk-claude-agent" style="display:none">
|
|
825
870
|
<button class="sk sk-claude" id="sk-inbox" data-sk="draft" onclick="openInboxSheet()" style="color:var(--yellow)">📥 草稿<span id="inbox-badge" style="display:none;margin-left:0.2rem;background:var(--yellow);color:#000;border-radius:8px;padding:0 0.32rem;font-size:0.66rem;font-weight:700"></span></button>
|
|
826
871
|
<button class="sk sk-claude" data-sk="folder" onclick="fpOpenFolder()" style="color:var(--blue2)">📂 文件夹</button>
|
|
827
|
-
<button class="sk sk-claude" data-sk="model" onclick="openModelSheet()">模型</button>
|
|
828
872
|
<button class="sk sk-claude" id="btn-plan" data-sk="plan" onclick="togglePlanMode()">计划</button>
|
|
829
873
|
<button class="sk sk-claude" data-sk="compact" onclick="doSlashCmd('/compact')">压缩</button>
|
|
830
874
|
<button class="sk sk-claude" data-sk="resume" onclick="openResumeSheet()">恢复</button>
|
|
@@ -883,6 +927,19 @@
|
|
|
883
927
|
<input type="file" id="fp-file-input" multiple style="display:none">
|
|
884
928
|
</div>
|
|
885
929
|
|
|
930
|
+
<!-- 文件预览:图片 / PDF / 音视频(浏览器原生能显示的),文件面板和聊天里的文件链接共用 -->
|
|
931
|
+
<div id="fp-viewer">
|
|
932
|
+
<div class="fv-head">
|
|
933
|
+
<div class="fv-title"><b id="fv-name"></b><span id="fv-path"></span></div>
|
|
934
|
+
<button class="fp-btn" id="fv-open" title="在浏览器新标签页里打开">↗ 新标签</button>
|
|
935
|
+
<button class="fp-btn" id="fv-dl" title="下载">⬇</button>
|
|
936
|
+
<button class="fp-btn" onclick="fpClosePreview()" title="关闭 (Esc)">✕</button>
|
|
937
|
+
</div>
|
|
938
|
+
<div class="fv-body" id="fv-body"></div>
|
|
939
|
+
<button class="fv-nav prev" id="fv-prev" onclick="fpPreviewStep(-1)" title="上一张 (←)">‹</button>
|
|
940
|
+
<button class="fv-nav next" id="fv-next" onclick="fpPreviewStep(1)" title="下一张 (→)">›</button>
|
|
941
|
+
</div>
|
|
942
|
+
|
|
886
943
|
<!-- 设置面板 -->
|
|
887
944
|
<div id="settings-backdrop" onclick="closeSettings()"></div>
|
|
888
945
|
<div id="settings-panel">
|
|
@@ -925,7 +982,6 @@
|
|
|
925
982
|
<div class="setting-group" data-stab="task" style="display:none">
|
|
926
983
|
<div class="set-task-hint">这些设置只对当前任务(或它所在的项目)生效,快捷栏里隐藏了的也能从这里打开。</div>
|
|
927
984
|
<div class="set-task-grid">
|
|
928
|
-
<button onclick="closeSettings();openModelSheet()">🤖 模型</button>
|
|
929
985
|
<button onclick="closeSettings();openGuardSheet()">🛡 隐私护栏</button>
|
|
930
986
|
<button onclick="closeSettings();openSecretsSheet()">🔑 密钥</button>
|
|
931
987
|
<button onclick="closeSettings();openFollowUpSheet()">🔄 自动跟进</button>
|
|
@@ -961,6 +1017,13 @@
|
|
|
961
1017
|
</div>
|
|
962
1018
|
</div>
|
|
963
1019
|
|
|
1020
|
+
<div id="model-pop-ov" onclick="closeModelPop()"></div>
|
|
1021
|
+
<div id="model-pop">
|
|
1022
|
+
<div id="model-pop-list"></div>
|
|
1023
|
+
<div class="mp-foot">
|
|
1024
|
+
<a id="model-pop-cfg" href="/?settings=1" target="_top">⚙ 配置模型</a>
|
|
1025
|
+
</div>
|
|
1026
|
+
</div>
|
|
964
1027
|
<div id="model-sheet" class="action-sheet">
|
|
965
1028
|
<div class="action-sheet-backdrop" onclick="closeModelSheet()"></div>
|
|
966
1029
|
<div class="action-sheet-box">
|
|
@@ -1311,6 +1374,7 @@
|
|
|
1311
1374
|
<button class="pri" onmousedown="event.preventDefault()" onclick="askFromSelection(false)">💡 解释</button>
|
|
1312
1375
|
<button onmousedown="event.preventDefault()" onclick="askFromSelection(true)">❓ 问一问…</button>
|
|
1313
1376
|
<button onmousedown="event.preventDefault()" onclick="askCopySelection()">📋 复制</button>
|
|
1377
|
+
<button onmousedown="event.preventDefault()" onclick="askSendToAI()" title="把选中的内容引用到输入框,接着写要求发给当前任务的 AI">💬 发给 AI</button>
|
|
1314
1378
|
</div>
|
|
1315
1379
|
<div id="ask-panel" role="dialog" aria-label="问一问">
|
|
1316
1380
|
<div class="ask-head">
|
|
@@ -2508,7 +2572,22 @@
|
|
|
2508
2572
|
return null;
|
|
2509
2573
|
}
|
|
2510
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
|
+
|
|
2511
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
|
+
}
|
|
2512
2591
|
if (msg.type === 'assistant' && Array.isArray(msg.message?.content)) {
|
|
2513
2592
|
for (const b of msg.message.content) { const k = blockKey(b, msg.message.id); if (k) _replayedBlocks.add(k); }
|
|
2514
2593
|
} else if (msg.type === 'tool_result' && msg.id) _replayedBlocks.add('tr:' + msg.id);
|
|
@@ -2541,11 +2620,21 @@
|
|
|
2541
2620
|
const sel = buildSyntheticUserEl(msg.content);
|
|
2542
2621
|
if (sel) { parent.appendChild(sel); return; }
|
|
2543
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
|
+
}
|
|
2544
2632
|
const el = document.createElement('div');
|
|
2545
2633
|
el.className = 'msg msg-input' + (msg.inject ? ' msg-inject' : '');
|
|
2546
2634
|
const full = msg.timestamp ? new Date(msg.timestamp).toLocaleString('zh-CN') : '';
|
|
2547
|
-
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>`;
|
|
2548
2636
|
parent.appendChild(el);
|
|
2637
|
+
if (vis) parent.appendChild(visionEl(vis));
|
|
2549
2638
|
return;
|
|
2550
2639
|
}
|
|
2551
2640
|
|
|
@@ -3085,6 +3174,9 @@
|
|
|
3085
3174
|
updateControlUI(session.controlHolder === socket.id, session.controlHolderName);
|
|
3086
3175
|
|
|
3087
3176
|
claudeSession = isClaudeSession(session) || session.mode === 'agent';
|
|
3177
|
+
// 模型按钮只在配了自定义网关时出现(列表来自网关),由 refreshModelChip 决定显隐
|
|
3178
|
+
if (session.mode !== 'agent') document.getElementById('model-chip').style.display = 'none';
|
|
3179
|
+
if (session.mode === 'agent') refreshModelChip();
|
|
3088
3180
|
updateShortcutBar();
|
|
3089
3181
|
updatePlanButton();
|
|
3090
3182
|
ctxUpdate(session.contextTokens || 0);
|
|
@@ -3154,6 +3246,7 @@
|
|
|
3154
3246
|
_ctxNudgeLevel = 0; _lastCtxTokens = 0; ctxUpdate(0);
|
|
3155
3247
|
}
|
|
3156
3248
|
addStatusMessage(msg.message);
|
|
3249
|
+
if (msg.vision) { chatArea.appendChild(visionEl(msg)); scrollToBottom(); }
|
|
3157
3250
|
// 同步计划模式状态到按钮
|
|
3158
3251
|
if (msg.message.includes('进入计划模式')) {
|
|
3159
3252
|
if (currentSession) currentSession.permissionMode = 'plan';
|
|
@@ -3671,6 +3764,11 @@
|
|
|
3671
3764
|
saveDraftFromInput('busy');
|
|
3672
3765
|
return;
|
|
3673
3766
|
}
|
|
3767
|
+
// 网关积分用尽:发出去大概率被网关拒,先提醒一句(本地 / 斜杠命令不算)
|
|
3768
|
+
if (currentSession?.mode === 'agent' && !text.startsWith('/')) {
|
|
3769
|
+
const q = quotaState();
|
|
3770
|
+
if (q.out && !confirm(q.msg + '\n\n发出去大概率会被网关拒绝。仍要发送吗?')) return;
|
|
3771
|
+
}
|
|
3674
3772
|
|
|
3675
3773
|
if (!isController) {
|
|
3676
3774
|
if (canTakeControl()) { socket.emit('take-control', { sessionId: SESSION_ID }); }
|
|
@@ -4079,7 +4177,7 @@
|
|
|
4079
4177
|
<b>让它读文件</b>:打 <b>@</b> 从本项目逐层浏览、打字搜索;项目外打 <b>@D:</b>、<b>@/</b>、<b>@~/</b> 按路径逐层浏览;选中插路径;或点「文件」面板的 <b>@</b> 引用。<br>
|
|
4080
4178
|
<b>工具/思考</b>:AI 干活的过程默认折叠,点「🔧 N 个工具调用」展开看命令与输出。<br>
|
|
4081
4179
|
<b>AI 忙时</b>:想补充或提醒,点输入框右边「⚡ 插话」——不打断,AI 下一步就会看到并调整(气泡上显示「AI 已看到」);不急的就回车「存草稿」排队,草稿里可排顺序、编辑、合并,开「依次自动发送」后每干完一轮自动发下一条,AI 忙时也能把草稿「⚡ 插入」当前任务。<br>
|
|
4082
|
-
<b>看不懂就问</b>:在聊天里<b>选中</b>看不懂的词或一段话(手机长按选中),点「💡 解释」或「❓ 问一问」,答案在旁边小窗里,可以继续追问;这些问答<b>不会打扰正在干活的 AI</b>,想让它知道就点「👉 告诉 AI
|
|
4180
|
+
<b>看不懂就问</b>:在聊天里<b>选中</b>看不懂的词或一段话(手机长按选中),点「💡 解释」或「❓ 问一问」,答案在旁边小窗里,可以继续追问;这些问答<b>不会打扰正在干活的 AI</b>,想让它知道就点「👉 告诉 AI」。想直接拿这段内容跟 AI 说事,点「💬 发给 AI」:内容会引用到输入框,接着写要求再发送。<br>
|
|
4083
4181
|
<b>AI 提问 / 交方案</b>:AI 需要你拿主意时会弹出选项,选一个即可,也可「跳过,让 AI 自己决定」;收起后输入框上方「❓ AI 在等你回答」可再打开。<br>
|
|
4084
4182
|
<b>定位旧消息</b>:电脑上鼠标移到聊天区,右下角 ☰ 可列出你发过的消息点击跳转;看完点底部「↓ 回到底部」一键回来。
|
|
4085
4183
|
${sec('顶栏')}
|
|
@@ -4180,10 +4278,10 @@
|
|
|
4180
4278
|
continue;
|
|
4181
4279
|
}
|
|
4182
4280
|
// 点整行 = 打开(文件夹进入 / 文件预览或编辑);其余操作都在行尾 ⋯(电脑上右键也行)
|
|
4183
|
-
const kind = e.isDir ? 'd' : isEditableFile(e.name) ? 'e' : 'f';
|
|
4281
|
+
const kind = e.isDir ? 'd' : isEditableFile(e.name) ? 'e' : previewKind(e.name) ? 'p' : 'f';
|
|
4184
4282
|
const a = `'${escAttr(rel)}','${kind}'`;
|
|
4185
4283
|
html += `<div class="fp-item" onclick="fpOpen(${a})" oncontextmenu="fpItemMenu(event,${a})" title="${kind === 'f' ? '点击查看可用操作' : '点击打开'}">
|
|
4186
|
-
<span class="fp-icon">${e.isDir ? '📁' : kind === 'e' ? '📝' : '📄'}</span>
|
|
4284
|
+
<span class="fp-icon">${e.isDir ? '📁' : kind === 'e' ? '📝' : kind === 'p' ? ({ img: '🖼', pdf: '📕', video: '🎬', audio: '🎵' })[previewKind(e.name)] : '📄'}</span>
|
|
4187
4285
|
<span class="fp-name">${escHtml(e.name)}</span>
|
|
4188
4286
|
${e.isDir ? '' : `<span class="fp-size">${fpFormatSize(e.size)}</span>`}
|
|
4189
4287
|
<span class="fp-more" onclick="event.stopPropagation();fpItemMenu(event,${a})" title="更多操作">⋯</span>
|
|
@@ -4250,6 +4348,7 @@
|
|
|
4250
4348
|
if (fpSel) return fpToggleSel(rel);
|
|
4251
4349
|
if (kind === 'd') return fpNav(rel);
|
|
4252
4350
|
if (kind === 'e') return openFileEditor(rel);
|
|
4351
|
+
if (kind === 'p') return fpPreview(rel);
|
|
4253
4352
|
fpItemMenu(null, rel, kind);
|
|
4254
4353
|
};
|
|
4255
4354
|
window.fpCopyPath = function(rel) {
|
|
@@ -4292,11 +4391,73 @@
|
|
|
4292
4391
|
if (kind === 'd') items.push(['📂 打开', () => fpNav(rel)], ['⬇ 打包下载', () => fpZip([rel]), 'zip']);
|
|
4293
4392
|
else {
|
|
4294
4393
|
if (kind === 'e') items.push([/\.(html?|md|markdown)$/i.test(rel) ? '👁 查看 / 编辑' : '✏️ 编辑', () => openFileEditor(rel)]);
|
|
4394
|
+
if (kind === 'p') items.push(['👁 预览', () => fpPreview(rel)]);
|
|
4295
4395
|
items.push(['⬇ 下载', () => fpDownload(rel)]);
|
|
4296
4396
|
}
|
|
4297
4397
|
items.push(['@ 引用到消息', () => fpRefer(kind === 'd' ? rel + '/' : rel), '让 AI 读它'], ['📋 复制路径', () => fpCopyPath(rel)]);
|
|
4298
4398
|
fpMenu(ev, rel, items);
|
|
4299
4399
|
};
|
|
4400
|
+
// ── 文件预览:图片 / PDF / 音视频 —— 浏览器自己能显示的直接看,不用先下载 ──────────
|
|
4401
|
+
window.previewKind = function(name) {
|
|
4402
|
+
const e = (String(name).toLowerCase().match(/\.([a-z0-9]+)$/) || [])[1] || '';
|
|
4403
|
+
if (/^(png|jpe?g|gif|webp|bmp|ico|avif|svg)$/.test(e)) return 'img';
|
|
4404
|
+
if (e === 'pdf') return 'pdf';
|
|
4405
|
+
if (/^(mp4|webm|ogv|mov|m4v)$/.test(e)) return 'video';
|
|
4406
|
+
if (/^(mp3|wav|ogg|oga|m4a|flac|aac|opus)$/.test(e)) return 'audio';
|
|
4407
|
+
return null;
|
|
4408
|
+
};
|
|
4409
|
+
let _fvList = [], _fvIdx = 0;
|
|
4410
|
+
const fvUrl = (rel, inline) => `/api/files/${SESSION_ID}/download?path=${encodeURIComponent(rel)}${inline ? '&inline=1' : ''}`;
|
|
4411
|
+
window.fpPreview = function(rel) {
|
|
4412
|
+
// 同一目录的图片可以左右翻(从文件面板打开时)
|
|
4413
|
+
const dir = rel.includes('/') ? rel.slice(0, rel.lastIndexOf('/')) : '';
|
|
4414
|
+
const sameDirImgs = previewKind(rel) === 'img' && dir === fpCurrentPath ? fpVisible.filter(r => previewKind(r) === 'img') : [];
|
|
4415
|
+
_fvList = sameDirImgs.length > 1 ? sameDirImgs : [rel];
|
|
4416
|
+
_fvIdx = Math.max(0, _fvList.indexOf(rel));
|
|
4417
|
+
document.getElementById('fp-viewer').classList.add('open');
|
|
4418
|
+
fvShow();
|
|
4419
|
+
};
|
|
4420
|
+
function fvShow() {
|
|
4421
|
+
const rel = _fvList[_fvIdx], kind = previewKind(rel), url = fvUrl(rel, true);
|
|
4422
|
+
const v = document.getElementById('fp-viewer'), body = document.getElementById('fv-body');
|
|
4423
|
+
v.classList.toggle('multi', _fvList.length > 1);
|
|
4424
|
+
document.getElementById('fv-name').textContent = rel.split('/').pop() + (_fvList.length > 1 ? ` (${_fvIdx + 1}/${_fvList.length})` : '');
|
|
4425
|
+
document.getElementById('fv-path').textContent = typeof feditAbs === 'function' ? feditAbs(rel) : rel;
|
|
4426
|
+
document.getElementById('fv-open').onclick = () => window.open(url, '_blank');
|
|
4427
|
+
document.getElementById('fv-dl').onclick = () => fpDownload(rel);
|
|
4428
|
+
body.classList.remove('zoomed');
|
|
4429
|
+
if (kind === 'img') {
|
|
4430
|
+
body.innerHTML = `<img src="${escAttr(url)}" alt="">`;
|
|
4431
|
+
const img = body.querySelector('img');
|
|
4432
|
+
img.onclick = () => { img.classList.toggle('zoom'); body.classList.toggle('zoomed', img.classList.contains('zoom')); }; // 点击看原尺寸
|
|
4433
|
+
img.onerror = () => { body.innerHTML = '<div class="fv-msg">图片打不开(可能已被删除或格式损坏)</div>'; };
|
|
4434
|
+
} else if (kind === 'video') {
|
|
4435
|
+
body.innerHTML = `<video src="${escAttr(url)}" controls autoplay playsinline></video>`;
|
|
4436
|
+
} else if (kind === 'audio') {
|
|
4437
|
+
body.innerHTML = `<audio src="${escAttr(url)}" controls autoplay></audio>`;
|
|
4438
|
+
} else if (kind === 'pdf') {
|
|
4439
|
+
// 手机浏览器大多不能把 PDF 嵌在页面里(安卓会直接下载):给按钮在新标签打开
|
|
4440
|
+
if (matchMedia('(pointer: coarse)').matches) {
|
|
4441
|
+
body.innerHTML = `<div class="fv-msg">手机浏览器不能在页面里直接显示 PDF<br><button class="fp-btn" style="margin-top:0.8rem;padding:0.5rem 1rem" onclick="window.open('${escAttr(url)}','_blank')">↗ 在新标签打开</button></div>`;
|
|
4442
|
+
} else body.innerHTML = `<iframe src="${escAttr(url)}"></iframe>`;
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
window.fpPreviewStep = function(d) {
|
|
4446
|
+
if (_fvList.length < 2) return;
|
|
4447
|
+
_fvIdx = (_fvIdx + d + _fvList.length) % _fvList.length; fvShow();
|
|
4448
|
+
};
|
|
4449
|
+
window.fpClosePreview = function() {
|
|
4450
|
+
const v = document.getElementById('fp-viewer');
|
|
4451
|
+
v.classList.remove('open');
|
|
4452
|
+
document.getElementById('fv-body').innerHTML = ''; // 停掉正在播放的音视频
|
|
4453
|
+
};
|
|
4454
|
+
document.addEventListener('keydown', (e) => {
|
|
4455
|
+
if (!document.getElementById('fp-viewer').classList.contains('open')) return;
|
|
4456
|
+
if (e.key === 'Escape') fpClosePreview();
|
|
4457
|
+
else if (e.key === 'ArrowLeft') fpPreviewStep(-1);
|
|
4458
|
+
else if (e.key === 'ArrowRight') fpPreviewStep(1);
|
|
4459
|
+
});
|
|
4460
|
+
|
|
4300
4461
|
window.fpToolsMenu = function(ev) {
|
|
4301
4462
|
const items = [
|
|
4302
4463
|
['🔄 同步到本地文件夹', () => fpStartSync(), 'Chrome / Edge'],
|
|
@@ -4433,7 +4594,13 @@
|
|
|
4433
4594
|
const rel = feditNormPath(String(rawPath || '').trim());
|
|
4434
4595
|
if (!rel) { addStatusMessage('路径为空'); return; }
|
|
4435
4596
|
if (!isEditableFile(rel)) {
|
|
4436
|
-
|
|
4597
|
+
// 图片 / PDF / 音视频:浏览器能直接显示,打开预览(AI 只写文件名时在任务目录里按名字找)
|
|
4598
|
+
if (previewKind(rel)) {
|
|
4599
|
+
let p = rel;
|
|
4600
|
+
if (!rel.includes('/')) { const hit = await feditFindByName(rel); if (hit) p = hit; }
|
|
4601
|
+
return fpPreview(p);
|
|
4602
|
+
}
|
|
4603
|
+
addStatusMessage('「' + rel.split('/').pop() + '」浏览器打不开这种文件,请在文件面板里下载');
|
|
4437
4604
|
return;
|
|
4438
4605
|
}
|
|
4439
4606
|
const sheet = document.getElementById('fedit-sheet');
|
|
@@ -4460,14 +4627,15 @@
|
|
|
4460
4627
|
return;
|
|
4461
4628
|
}
|
|
4462
4629
|
const html = /\.html?$/i.test(d.name || d.path || '');
|
|
4463
|
-
|
|
4630
|
+
const svg = /\.svg$/i.test(d.name || d.path || '');
|
|
4631
|
+
_fedit = { path: d.path, name: d.name, mtime: d.mtime, md: !!d.md, html, svg, mode: 'edit', loaded: d.content, dirty: false };
|
|
4464
4632
|
document.getElementById('fedit-path').textContent = feditAbs(d.path);
|
|
4465
4633
|
document.getElementById('fedit-ta').value = d.content;
|
|
4466
4634
|
document.getElementById('fedit-loading').style.display = 'none';
|
|
4467
|
-
document.getElementById('fedit-tab-preview').style.display = (_fedit.md || html) ? '' : 'none';
|
|
4635
|
+
document.getElementById('fedit-tab-preview').style.display = (_fedit.md || html || svg) ? '' : 'none';
|
|
4468
4636
|
document.getElementById('fedit-newtab').style.display = html ? '' : 'none';
|
|
4469
4637
|
// 文档类(md / html)默认「看」,要改再点 ✏️ 编辑
|
|
4470
|
-
feditSetMode(_fedit.md || html ? 'preview' : 'edit');
|
|
4638
|
+
feditSetMode(_fedit.md || html || svg ? 'preview' : 'edit');
|
|
4471
4639
|
} catch (e) {
|
|
4472
4640
|
addStatusMessage('打开出错: ' + e.message);
|
|
4473
4641
|
sheet.classList.remove('open');
|
|
@@ -4526,15 +4694,21 @@
|
|
|
4526
4694
|
|
|
4527
4695
|
window.feditSetMode = function(mode) {
|
|
4528
4696
|
if (!_fedit) return;
|
|
4529
|
-
if (mode === 'preview' && !_fedit.md && !_fedit.html) mode = 'edit';
|
|
4697
|
+
if (mode === 'preview' && !_fedit.md && !_fedit.html && !_fedit.svg) mode = 'edit';
|
|
4530
4698
|
_fedit.mode = mode;
|
|
4531
4699
|
const ta = document.getElementById('fedit-ta');
|
|
4532
4700
|
const pv = document.getElementById('fedit-preview');
|
|
4533
4701
|
ta.style.display = mode === 'edit' ? 'block' : 'none';
|
|
4534
4702
|
pv.style.display = mode === 'preview' ? 'block' : 'none';
|
|
4535
|
-
pv.classList.toggle('html', mode === 'preview' && !!_fedit.html);
|
|
4703
|
+
pv.classList.toggle('html', mode === 'preview' && !!(_fedit.html || _fedit.svg));
|
|
4536
4704
|
document.getElementById('fedit-icon').textContent = mode === 'preview' ? '📄' : '✏️';
|
|
4537
|
-
if (mode === 'preview') {
|
|
4705
|
+
if (mode === 'preview') {
|
|
4706
|
+
if (_fedit.html) feditRenderHtml(pv, ta.value);
|
|
4707
|
+
else if (_fedit.svg) { // SVG 用 <img> 渲染:图片上下文里内嵌脚本不执行;用编辑框内容,没保存的修改也能看
|
|
4708
|
+
const u = URL.createObjectURL(new Blob([ta.value], { type: 'image/svg+xml' }));
|
|
4709
|
+
pv.innerHTML = `<div style="height:100%;display:flex;align-items:center;justify-content:center;padding:0.5rem;box-sizing:border-box"><img src="${u}" style="max-width:100%;max-height:100%;object-fit:contain"></div>`;
|
|
4710
|
+
} else pv.innerHTML = renderMarkdown(ta.value);
|
|
4711
|
+
}
|
|
4538
4712
|
document.getElementById('fedit-tab-edit').classList.toggle('on', mode === 'edit');
|
|
4539
4713
|
document.getElementById('fedit-tab-preview').classList.toggle('on', mode === 'preview');
|
|
4540
4714
|
};
|
|
@@ -5451,19 +5625,109 @@
|
|
|
5451
5625
|
if (e.key === 'Enter') { e.preventDefault(); submitAskReply(); }
|
|
5452
5626
|
});
|
|
5453
5627
|
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5628
|
+
// ── 模型:聊天框里的「模型」按钮 + 上方弹出列表 ─────────────────────────────
|
|
5629
|
+
// 列表 = myhi 本地配置(场景模型)+ 网关模型目录(这个 token 能用的,按厂商分组),当前会话的打勾
|
|
5630
|
+
const CAP_ICON = { vision: '🖼', function_calling: '🧰', reasoning: '🧠' };
|
|
5631
|
+
const TAG_LABEL = { recommended: ['推荐', 'var(--purple)'], cheap: ['便宜', 'var(--green)'], vision: ['看图', 'var(--blue)'], fast: ['快', 'var(--yellow)'], experimental: ['实验', 'var(--muted)'], deprecated: ['将下线', 'var(--red)'] };
|
|
5632
|
+
let _modelInfo = null; // /api/models 的结果(服务端缓存 10 分钟)
|
|
5633
|
+
async function loadModelInfo(force) {
|
|
5634
|
+
if (_modelInfo && !force) return _modelInfo;
|
|
5635
|
+
try { _modelInfo = await (await fetch('/api/models')).json(); } catch { _modelInfo = _modelInfo || {}; }
|
|
5636
|
+
return _modelInfo;
|
|
5637
|
+
}
|
|
5638
|
+
const modelName = (id) => { const m = (_modelInfo?.models || []).find(x => x.id === id); return m && m.kind !== 'alias' ? m.name : id; };
|
|
5639
|
+
// 网关积分(新网关的 /v1/models?scope=token 才带;网关按积分计费,1 积分 = $0.01):已用 / 上限,用尽时发消息前提醒
|
|
5640
|
+
function quotaState() {
|
|
5641
|
+
const t = _modelInfo?.token;
|
|
5642
|
+
if (!t) return { text: '', out: false };
|
|
5643
|
+
const fmt = (u, l) => `${u ?? '?'} / ${l} 积分`;
|
|
5644
|
+
const parts = [];
|
|
5645
|
+
if (t.dailyLimit != null) parts.push('今日已用 ' + fmt(t.dailyUsed, t.dailyLimit));
|
|
5646
|
+
if (t.monthlyLimit != null) parts.push('本月已用 ' + fmt(t.monthlyUsed, t.monthlyLimit));
|
|
5647
|
+
const dayOut = t.dailyLimit != null && t.dailyUsed != null && t.dailyUsed >= t.dailyLimit;
|
|
5648
|
+
const monOut = t.monthlyLimit != null && t.monthlyUsed != null && t.monthlyUsed >= t.monthlyLimit;
|
|
5649
|
+
const out = dayOut || monOut;
|
|
5650
|
+
return { text: parts.length ? '积分:' + parts.join(' · ') : '', out,
|
|
5651
|
+
msg: out ? `网关 token${t.name ? '「' + t.name + '」' : ''}${dayOut ? '今日' : '本月'}积分已用完(${dayOut ? fmt(t.dailyUsed, t.dailyLimit) : fmt(t.monthlyUsed, t.monthlyLimit)})` : '' };
|
|
5652
|
+
}
|
|
5653
|
+
// 额度会变:每 5 分钟后台刷新一次(服务端本身也缓存 10 分钟)
|
|
5654
|
+
setInterval(() => { if (currentSession?.mode === 'agent' && !document.hidden) loadModelInfo(true); }, 5 * 60_000);
|
|
5655
|
+
window.refreshModelChip = async function() {
|
|
5656
|
+
await loadModelInfo();
|
|
5657
|
+
const cur = currentSession?.modelId || '';
|
|
5658
|
+
const main = _modelInfo?.scenes?.main || '';
|
|
5659
|
+
// 计划模式且系统设置里配了「计划」模型 → 这段时间实际用它(批准执行后回到主任务模型)
|
|
5660
|
+
const plan = currentSession?.permissionMode === 'plan' ? (_modelInfo?.scenes?.plan || '') : '';
|
|
5661
|
+
const el = document.getElementById('model-chip-name');
|
|
5662
|
+
el.textContent = plan ? '计划·' + modelName(plan) : cur ? modelName(cur) : main ? modelName(main) : '默认';
|
|
5663
|
+
const chip = document.getElementById('model-chip'), q = quotaState();
|
|
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) : '');
|
|
5667
|
+
chip.style.borderColor = q.out ? 'var(--red)' : ''; // 积分用尽:按钮描红
|
|
5668
|
+
};
|
|
5669
|
+
window.openModelPop = async function() {
|
|
5670
|
+
const pop = document.getElementById('model-pop'), ov = document.getElementById('model-pop-ov'), list = document.getElementById('model-pop-list');
|
|
5671
|
+
list.innerHTML = '<div class="mp-h">加载模型列表…</div>';
|
|
5672
|
+
pop.classList.add('open'); ov.classList.add('open');
|
|
5673
|
+
placeModelPop();
|
|
5674
|
+
const d = await loadModelInfo(true);
|
|
5675
|
+
const cur = currentSession?.modelId || '';
|
|
5676
|
+
const main = d.scenes?.main || '';
|
|
5677
|
+
// unfit:不适合当主会话模型的原因(不支持工具等)→ 变灰 + 说明,点了先确认
|
|
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>`;
|
|
5679
|
+
const q = quotaState();
|
|
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>` : '';
|
|
5681
|
+
html += row('default', '默认', main ? '主任务默认:' + main : '跟随 Claude Code 默认', '', [], !cur);
|
|
5682
|
+
const gm = (d.models || []).filter(m => !m.id.includes('*')); // 通配别名(claude-*)不能直接选
|
|
5683
|
+
const listed = new Set(['default']);
|
|
5684
|
+
if (gm.length) {
|
|
5685
|
+
const by = {};
|
|
5686
|
+
for (const m of gm) (by[m.provider || '其他'] ||= []).push(m);
|
|
5687
|
+
for (const [prov, ms] of Object.entries(by)) {
|
|
5688
|
+
html += `<div class="mp-h">${escHtml(prov)}${d.scoped ? '' : ' · 网关未按 token 过滤'}</div>`;
|
|
5689
|
+
for (const m of ms) {
|
|
5690
|
+
listed.add(m.id);
|
|
5691
|
+
const sub = [m.kind === 'alias' && m.target ? '→ ' + m.target : '', m.caps.map(c => CAP_ICON[c]).filter(Boolean).join(' ')].filter(Boolean).join(' · ');
|
|
5692
|
+
// 右侧:有渠道系数就显示倍率(如 0.51x),否则显示单价;单价放悬停提示里
|
|
5693
|
+
const price = m.inputCost != null ? `$${m.inputCost}/$${m.outputCost} 每百万 token` : '';
|
|
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(' · '));
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5700
|
+
} else if (d.gateway && d.error) html += `<div class="mp-h">网关模型列表读取失败:${escHtml(d.error)}</div>`;
|
|
5701
|
+
// 自定义:本地场景配置里填过、或本会话正在用、但不在网关列表里的名字
|
|
5702
|
+
const custom = [...new Set([cur, ...Object.values(d.scenes || {})].filter(x => x && !listed.has(x)))];
|
|
5703
|
+
if (custom.length) { html += '<div class="mp-h">自定义模型</div>'; for (const id of custom) { listed.add(id); html += row(id, id, '', '', [], cur === id); } }
|
|
5704
|
+
// 模型只从网关目录来;没配自定义网关就不提供选择
|
|
5705
|
+
if (!d.gateway) html = '<div class="mp-h" style="white-space:normal;line-height:1.5">没有配置自定义网关,使用 Claude Code 默认模型。<br>配置网关后,这里会列出网关上这个 token 可用的模型。</div>';
|
|
5706
|
+
list.innerHTML = html;
|
|
5707
|
+
list.querySelectorAll('[data-model]').forEach(el => el.onclick = () => {
|
|
5708
|
+
if (el.dataset.unfit && !confirm(`「${el.dataset.label}」${el.dataset.unfit}。\n\n用它当主会话模型,AI 很可能干不了活。仍要切换吗?`)) return;
|
|
5709
|
+
closeModelPop(); selectModel({ id: el.dataset.model, label: el.dataset.label });
|
|
5464
5710
|
});
|
|
5465
|
-
|
|
5466
|
-
|
|
5711
|
+
// 只有管理员能改场景模型:成员隐藏「配置模型」
|
|
5712
|
+
document.getElementById('model-pop-cfg').style.display = myRole === 'admin' ? '' : 'none';
|
|
5713
|
+
list.querySelector('.mp-i.on')?.scrollIntoView({ block: 'nearest' });
|
|
5714
|
+
placeModelPop();
|
|
5715
|
+
};
|
|
5716
|
+
// 贴着按钮弹在上方、右对齐;按钮不可见(PTY 会话 / 从设置菜单打开)时居中
|
|
5717
|
+
function placeModelPop() {
|
|
5718
|
+
const pop = document.getElementById('model-pop'), chip = document.getElementById('model-chip');
|
|
5719
|
+
const r = chip.getBoundingClientRect(), w = pop.offsetWidth, h = pop.offsetHeight;
|
|
5720
|
+
if (chip.offsetParent) {
|
|
5721
|
+
pop.style.left = Math.max(8, Math.min(r.right - w, innerWidth - w - 8)) + 'px';
|
|
5722
|
+
pop.style.top = Math.max(8, r.top - h - 6) + 'px';
|
|
5723
|
+
} else { pop.style.left = Math.max(8, (innerWidth - w) / 2) + 'px'; pop.style.top = Math.max(8, (innerHeight - h) / 2) + 'px'; }
|
|
5724
|
+
}
|
|
5725
|
+
window.closeModelPop = function() {
|
|
5726
|
+
document.getElementById('model-pop').classList.remove('open');
|
|
5727
|
+
document.getElementById('model-pop-ov').classList.remove('open');
|
|
5728
|
+
};
|
|
5729
|
+
// 旧入口(终端会话快捷栏「模型」、设置菜单)都改走新弹层
|
|
5730
|
+
window.openModelSheet = function() { openModelPop(); };
|
|
5467
5731
|
window.closeModelSheet = function() {
|
|
5468
5732
|
document.getElementById('model-sheet').classList.remove('open');
|
|
5469
5733
|
};
|
|
@@ -5483,7 +5747,8 @@
|
|
|
5483
5747
|
}, 100);
|
|
5484
5748
|
}
|
|
5485
5749
|
currentModelId = m.id;
|
|
5486
|
-
|
|
5750
|
+
if (currentSession) currentSession.modelId = m.id === 'default' ? null : m.id; // 服务端会持久化,重启后照样用
|
|
5751
|
+
refreshModelChip();
|
|
5487
5752
|
}
|
|
5488
5753
|
window.selectCustomModel = function() {
|
|
5489
5754
|
const v = (document.getElementById('model-custom').value || '').trim();
|
|
@@ -6398,6 +6663,7 @@
|
|
|
6398
6663
|
btn.textContent = isPlan ? '退出计划' : '计划';
|
|
6399
6664
|
btn.style.color = isPlan ? 'var(--orange)' : '';
|
|
6400
6665
|
btn.style.fontWeight = isPlan ? '600' : '';
|
|
6666
|
+
if (currentSession?.mode === 'agent' && window.refreshModelChip) refreshModelChip(); // 计划模型可能不同
|
|
6401
6667
|
}
|
|
6402
6668
|
window.togglePlanMode = function() {
|
|
6403
6669
|
const isPlan = currentSession?.permissionMode === 'plan';
|
|
@@ -7342,6 +7608,19 @@
|
|
|
7342
7608
|
if (custom) { renderAsk(); askInput.focus(); }
|
|
7343
7609
|
else askSend('');
|
|
7344
7610
|
};
|
|
7611
|
+
// 「发给 AI」:选中的内容以引用格式放进输入框,光标停在下面,接着写要求再发(不直接发出去)
|
|
7612
|
+
window.askSendToAI = function() {
|
|
7613
|
+
if (!_askSel) return;
|
|
7614
|
+
const quote = _askSel.text.trim().split('\n').map(l => '> ' + l).join('\n');
|
|
7615
|
+
askHidePop();
|
|
7616
|
+
window.getSelection()?.removeAllRanges();
|
|
7617
|
+
const cur = cmdInput.value.replace(/\s+$/, '');
|
|
7618
|
+
cmdInput.value = (cur ? cur + '\n\n' : '') + quote + '\n\n';
|
|
7619
|
+
cmdInput.focus();
|
|
7620
|
+
cmdInput.setSelectionRange(cmdInput.value.length, cmdInput.value.length);
|
|
7621
|
+
cmdInput.dispatchEvent(new Event('input')); // 让输入框按内容长高
|
|
7622
|
+
cmdInput.scrollTop = cmdInput.scrollHeight;
|
|
7623
|
+
};
|
|
7345
7624
|
window.askCopySelection = function() {
|
|
7346
7625
|
if (!_askSel) return;
|
|
7347
7626
|
const t = _askSel.text;
|
package/dist/chat.html.gz
CHANGED
|
Binary file
|