@wendongfly/myhi 1.3.208 → 1.3.210

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 CHANGED
@@ -1021,6 +1021,7 @@
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=''}">
1024
1025
  <a id="model-pop-cfg" href="/?settings=1" target="_top">⚙ 配置模型</a>
1025
1026
  </div>
1026
1027
  </div>
@@ -3174,8 +3175,7 @@
3174
3175
  updateControlUI(session.controlHolder === socket.id, session.controlHolderName);
3175
3176
 
3176
3177
  claudeSession = isClaudeSession(session) || session.mode === 'agent';
3177
- // 模型按钮只在配了自定义网关时出现(列表来自网关),由 refreshModelChip 决定显隐
3178
- if (session.mode !== 'agent') document.getElementById('model-chip').style.display = 'none';
3178
+ document.getElementById('model-chip').style.display = session.mode === 'agent' ? '' : 'none';
3179
3179
  if (session.mode === 'agent') refreshModelChip();
3180
3180
  updateShortcutBar();
3181
3181
  updatePlanButton();
@@ -5661,8 +5661,6 @@
5661
5661
  const el = document.getElementById('model-chip-name');
5662
5662
  el.textContent = plan ? '计划·' + modelName(plan) : cur ? modelName(cur) : main ? modelName(main) : '默认';
5663
5663
  const chip = document.getElementById('model-chip'), q = quotaState();
5664
- // 没配自定义网关(直连 Anthropic 等):不提供模型选择,跟随 Claude Code 默认
5665
- chip.style.display = currentSession?.mode === 'agent' && _modelInfo?.gateway ? '' : 'none';
5666
5664
  chip.title = '切换模型 · 当前:' + (plan ? plan + '(计划模式,系统设置 · 计划)\n执行时:' : '') + (cur || (main ? main + '(主任务默认)' : 'Claude Code 默认')) + (q.text ? '\n' + (q.out ? q.msg : q.text) : '');
5667
5665
  chip.style.borderColor = q.out ? 'var(--red)' : ''; // 积分用尽:按钮描红
5668
5666
  };
@@ -5698,12 +5696,15 @@
5698
5696
  }
5699
5697
  }
5700
5698
  } else if (d.gateway && d.error) html += `<div class="mp-h">网关模型列表读取失败:${escHtml(d.error)}</div>`;
5699
+ // 没有自定义网关(Claude 原生):和原来一样列 Claude 模型;有网关:只列网关模型 + 下方手填自定义
5700
+ const claudeMs = d.gateway ? [] : MODELS.filter(x => x.id !== 'default');
5701
+ claudeMs.forEach(m => listed.add(m.id));
5701
5702
  // 自定义:本地场景配置里填过、或本会话正在用、但不在网关列表里的名字
5702
5703
  const custom = [...new Set([cur, ...Object.values(d.scenes || {})].filter(x => x && !listed.has(x)))];
5703
5704
  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>';
5705
+ if (claudeMs.length) { html += '<div class="mp-h">Claude 模型</div>'; for (const m of claudeMs) html += row(m.id, m.label, m.desc, '', [], cur === m.id); }
5706
5706
  list.innerHTML = html;
5707
+ document.getElementById('model-pop-custom').style.display = d.gateway ? '' : 'none';
5707
5708
  list.querySelectorAll('[data-model]').forEach(el => el.onclick = () => {
5708
5709
  if (el.dataset.unfit && !confirm(`「${el.dataset.label}」${el.dataset.unfit}。\n\n用它当主会话模型,AI 很可能干不了活。仍要切换吗?`)) return;
5709
5710
  closeModelPop(); selectModel({ id: el.dataset.model, label: el.dataset.label });
package/dist/chat.html.gz CHANGED
Binary file
package/dist/index.html CHANGED
@@ -956,6 +956,22 @@
956
956
  <button class="set-link" onclick="closeSettings();showUsage()">📊 用量 <small>费用 / token 统计</small></button>
957
957
  </div>
958
958
 
959
+ <!-- 大模型网关(仅管理员;单用户模式的主人就是管理员,不用再去 /admin)-->
960
+ <div id="set-gw" class="admin-only">
961
+ <div class="set-group-t">系统设置 · 大模型网关</div>
962
+ <div class="set-card">
963
+ <div class="set-item" style="display:block;font-size:0.74rem;color:var(--muted);line-height:1.6" id="set-gw-src">读取中…</div>
964
+ <div class="set-item inp"><div class="set-lbl">网关地址<small>留空 = 自动用这台电脑已有的配置(环境变量 / Claude Code 配置)</small></div>
965
+ <div class="set-ctl"><input class="set-inp" id="set-gw-url" placeholder="https://gateway.example.com" autocomplete="off" spellcheck="false"></div></div>
966
+ <div class="set-item inp"><div class="set-lbl">Token / API Key</div>
967
+ <div class="set-ctl"><input class="set-inp" id="set-gw-key" type="password" placeholder="Token / API Key" autocomplete="new-password"></div></div>
968
+ <label class="set-item" style="cursor:pointer;align-items:flex-start;gap:0.5rem"><input type="checkbox" id="set-gw-sync" style="margin-top:0.2rem">
969
+ <div class="set-lbl">同时写入 Claude Code 配置<small id="set-gw-sync-hint">终端里直接运行的 claude 也用这个网关。只改 ~/.claude/settings.json 里 env 的网关地址和 Key,其它设置不动,写之前先备份;取消勾选会把写进去的删掉</small></div></label>
970
+ <div class="set-item" style="justify-content:flex-end"><button class="set-btn" id="set-gw-save" onclick="saveGatewayCfg()">保存</button></div>
971
+ </div>
972
+ <div id="set-gw-status" class="set-status"></div>
973
+ </div>
974
+
959
975
  <!-- 场景模型:同一个网关 + token,不同场景用不同模型(仅管理员)-->
960
976
  <div id="set-models" class="admin-only">
961
977
  <div class="set-group-t">系统设置 · 模型(所有任务生效)</div>
@@ -1329,7 +1345,7 @@
1329
1345
  window.openSettings = function() {
1330
1346
  renderAppearance();
1331
1347
  ['set-machine-status', 'set-asr-status', 'set-restart-status', 'set-up-status', 'set-proto-status'].filter(id => document.getElementById(id)).forEach(id => setStatus(id, ''));
1332
- if (_isAdmin) { loadProto(); loadRegistry(); loadSceneModels(); }
1348
+ if (_isAdmin) { loadProto(); loadRegistry(); loadGatewayCfg(); loadSceneModels(); }
1333
1349
  document.querySelectorAll('#set-sheet .set-btn').forEach(b => b.classList.remove('dirty'));
1334
1350
  document.getElementById('set-backdrop').classList.add('open');
1335
1351
  document.getElementById('set-sheet').classList.add('open');
@@ -1505,6 +1521,34 @@
1505
1521
  voice: '语音转成书面文字,建议选便宜的;留空 = 同问一问',
1506
1522
  vision: '主任务模型看不了图时,先用它把聊天框发的图片转成文字再交给主模型(多花几秒,可在聊天里点开核对);留空 = 不转',
1507
1523
  };
1524
+ // ── 大模型网关 ────────────────────────────────────────────
1525
+ const GW_SRC = { myhi: '本页配置', env: '启动 myhi 时的环境变量(ANTHROPIC_BASE_URL)', claude: 'Claude Code 配置(~/.claude/settings.json)' };
1526
+ async function loadGatewayCfg() {
1527
+ let d; try { d = await (await fetch('/api/gateway')).json(); } catch { return; }
1528
+ const e = d.effective;
1529
+ document.getElementById('set-gw-src').textContent = !e
1530
+ ? '还没有自定义网关:AI 直连 Anthropic(或用 claude 登录的账号),不提供模型选择。填上网关地址和 Key 后,新建任务走网关,并可按场景从网关模型里选。'
1531
+ : e.source === 'myhi' ? `当前网关:${e.baseUrl}(Key ${e.apiKey})`
1532
+ : `当前网关:${e.baseUrl}(Key ${e.apiKey})——自动读取自 ${GW_SRC[e.source] || e.source}。要换成别的,在下面填写即可(这里填的优先)。`;
1533
+ document.getElementById('set-gw-url').value = d.baseUrl || '';
1534
+ document.getElementById('set-gw-key').value = '';
1535
+ document.getElementById('set-gw-key').placeholder = d.hasKey ? `已设置(${d.apiKey}),留空保持不变` : 'Token / API Key';
1536
+ const sync = document.getElementById('set-gw-sync');
1537
+ sync.checked = !!d.syncClaude;
1538
+ if (d.claudeSettings) document.getElementById('set-gw-sync-hint').textContent = `终端里直接运行的 claude 也用这个网关。只改 ${d.claudeSettings} 里 env 的网关地址和 Key,其它设置不动,写之前先备份(.myhi-bak);取消勾选会把写进去的删掉`;
1539
+ const btn = document.getElementById('set-gw-save');
1540
+ btn.classList.remove('dirty');
1541
+ ['set-gw-url', 'set-gw-key'].forEach(id => document.getElementById(id).oninput = () => btn.classList.add('dirty'));
1542
+ sync.onchange = () => btn.classList.add('dirty');
1543
+ }
1544
+ window.saveGatewayCfg = async function() {
1545
+ const baseUrl = document.getElementById('set-gw-url').value.trim(), apiKey = document.getElementById('set-gw-key').value.trim();
1546
+ if (baseUrl && !/^https?:\/\//i.test(baseUrl)) { setStatus('set-gw-status', '网关地址要以 http:// 或 https:// 开头'); return; }
1547
+ let r; try { r = await (await fetch('/api/gateway', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ baseUrl, apiKey, syncClaude: document.getElementById('set-gw-sync').checked }) })).json(); } catch (e) { r = { error: e.message }; }
1548
+ setStatus('set-gw-status', r.ok ? '已保存:新建的任务走新网关;正在跑的任务下次重启 AI 后生效' + (r.synced ? `。已同步到 ${r.synced},终端里新开的 claude 生效` : '') : '保存失败:' + (r.error || ''));
1549
+ if (r.ok) { await loadGatewayCfg(); fetch('/api/models?refresh=1').catch(() => {}).finally(loadSceneModels); }
1550
+ };
1551
+
1508
1552
  async function loadSceneModels() {
1509
1553
  let d; try { d = await (await fetch('/api/models')).json(); } catch { return; }
1510
1554
  if (!d.ok) return;
@@ -1516,7 +1560,7 @@
1516
1560
  const noGw = !d.gateway;
1517
1561
  document.getElementById('set-models-rows').style.display = noGw ? 'none' : '';
1518
1562
  document.getElementById('set-models-save').parentElement.style.display = noGw ? 'none' : '';
1519
- if (noGw) { src.textContent = '没有配置自定义网关:所有任务使用 Claude Code 默认模型,不需要选模型。配置网关后,可在这里按场景(主任务 / 计划 / 问一问 / 看图…)从网关模型里选。'; return; }
1563
+ if (noGw) { src.textContent = '没有配置自定义网关:所有任务使用 Claude Code 默认模型,不需要选模型。在上面「大模型网关」里配置后,可在这里按场景(主任务 / 计划 / 问一问 / 看图…)从网关模型里选。'; return; }
1520
1564
  src.textContent = d.error ? '网关模型列表读取失败(' + d.error + '),可以手填模型名。'
1521
1565
  : `网关上有 ${ms.length} 个模型${d.scoped ? '(这个 token 可用的)' : '(网关是旧版,未按 token 过滤,个别模型可能用不了)'},点输入框可选,也可以手填。` + quota;
1522
1566
  // 两份候选:主会话 / 子代理只列能用工具干活的;问一问、语音整理、后台小任务全列,便宜的排前面
Binary file