@bolloon/bolloon-agent 0.5.0 → 0.5.1
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/agents/execution-supervisor.js +87 -5
- package/dist/agents/model-policy.js +651 -0
- package/dist/agents/pi-sdk-tools.js +84 -33
- package/dist/agents/pi-sdk.js +83 -23
- package/dist/agents/run-store.js +53 -0
- package/dist/agents/task/task-runner.js +8 -1
- package/dist/agents/workflow-pivot-loop.js +19 -12
- package/dist/cli/clipboard.js +65 -0
- package/dist/cli/ink-app.js +145 -17
- package/dist/cli/log-gate.js +386 -0
- package/dist/cli/mention-data.js +2 -0
- package/dist/cli/model-selector.js +538 -0
- package/dist/cli/reply-hygiene.js +54 -0
- package/dist/cli/setup-wizard.js +686 -69
- package/dist/cli/startup-notice.js +160 -0
- package/dist/cli/stores.js +22 -1
- package/dist/cli/theme.js +49 -0
- package/dist/cli/tui-select.js +970 -0
- package/dist/cli-entry.js +125 -18
- package/dist/electron-build/agents/execution-supervisor.js +86 -4
- package/dist/electron-build/agents/execution-supervisor.js.map +1 -1
- package/dist/electron-build/agents/goal-store.js.map +1 -1
- package/dist/electron-build/agents/model-policy.js +706 -0
- package/dist/electron-build/agents/model-policy.js.map +1 -0
- package/dist/electron-build/agents/pi-sdk-tools.js +84 -33
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +83 -23
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/run-store.js +55 -1
- package/dist/electron-build/agents/run-store.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js +8 -1
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +19 -12
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +146 -6
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/connection-probe.js +764 -0
- package/dist/electron-build/llm/connection-probe.js.map +1 -0
- package/dist/electron-build/llm/custom-provider-store.js +272 -0
- package/dist/electron-build/llm/custom-provider-store.js.map +1 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js +23 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js.map +1 -0
- package/dist/electron-build/llm/model-catalog.js +778 -0
- package/dist/electron-build/llm/model-catalog.js.map +1 -0
- package/dist/electron-build/llm/model-discovery.js +1202 -0
- package/dist/electron-build/llm/model-discovery.js.map +1 -0
- package/dist/electron-build/llm/model-selection.js +1315 -0
- package/dist/electron-build/llm/model-selection.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +70 -11
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/llm/provider-catalog.js +969 -0
- package/dist/electron-build/llm/provider-catalog.js.map +1 -0
- package/dist/electron-build/llm/provider-registry.js +950 -0
- package/dist/electron-build/llm/provider-registry.js.map +1 -0
- package/dist/electron-build/llm/tool-name.js +266 -0
- package/dist/electron-build/llm/tool-name.js.map +1 -0
- package/dist/electron-build/utils/dual-source-facts.js +236 -0
- package/dist/electron-build/utils/dual-source-facts.js.map +1 -0
- package/dist/electron-build/utils/dual-source.js +32 -156
- package/dist/electron-build/utils/dual-source.js.map +1 -1
- package/dist/electron-build/utils/update-manager.js +22 -72
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/electron-build/utils/update-state.js +2 -19
- package/dist/electron-build/utils/update-state.js.map +1 -1
- package/dist/electron-build/utils/version-identity.js +193 -0
- package/dist/electron-build/utils/version-identity.js.map +1 -0
- package/dist/electron-build/utils/version-info.js +47 -101
- package/dist/electron-build/utils/version-info.js.map +1 -1
- package/dist/index.js +282 -96
- package/dist/ios/bolloon-web.json +10 -0
- package/dist/ios/mobile-contacts.js +44 -11
- package/dist/ios/mobile-core.js +1723 -84
- package/dist/ios/mobile-tasks.js +339 -0
- package/dist/ios/mobile-update.js +1442 -0
- package/dist/ios/mobile.js +308 -35
- package/dist/ios/routes-llm-config.js +344 -52
- package/dist/ios/routes-mobile-tasks.js +353 -0
- package/dist/ios/server.js +163 -4
- package/dist/ios/sw.js +1 -1
- package/dist/llm/config-store.js +146 -6
- package/dist/llm/connection-probe.js +744 -0
- package/dist/llm/custom-provider-store.js +224 -0
- package/dist/llm/data/provider-catalog-baked.js +19 -0
- package/dist/llm/model-catalog.js +717 -0
- package/dist/llm/model-discovery.js +1137 -0
- package/dist/llm/model-selection.js +1241 -0
- package/dist/llm/pi-ai.js +70 -11
- package/dist/llm/provider-catalog.js +898 -0
- package/dist/llm/provider-registry.js +917 -0
- package/dist/llm/tool-name.js +220 -0
- package/dist/setup/onboard.js +34 -8
- package/dist/utils/dual-source-facts.js +223 -0
- package/dist/utils/dual-source.js +12 -141
- package/dist/utils/update-manager.js +16 -59
- package/dist/utils/update-state.js +6 -18
- package/dist/utils/version-identity.js +176 -0
- package/dist/utils/version-info.js +9 -78
- package/dist/web/bolloon-web.json +10 -0
- package/dist/web/mobile-core.js +1721 -84
- package/dist/web/mobile-update.js +1442 -0
- package/dist/web/mobile.js +308 -35
- package/dist/web/routes-llm-config.js +344 -52
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/scripts/build-web.ts +36 -0
package/dist/ios/mobile.js
CHANGED
|
@@ -1697,6 +1697,110 @@
|
|
|
1697
1697
|
return r;
|
|
1698
1698
|
}
|
|
1699
1699
|
|
|
1700
|
+
/**
|
|
1701
|
+
* 「App 更新 (web 资源层)」页 (2026-09-26, update-protocol §13)
|
|
1702
|
+
* 看得见的: 当前装的是哪个源 / 哪个版本·sha / 能切回哪个源 —— 与桌面 `bolloon update --status` 同一口径。
|
|
1703
|
+
* 做得出的: 检查 → (智能体可自动跑到"准备并验证") → 人工确认切换 → 重载; 一键回滚。
|
|
1704
|
+
* 说不出的不说: 原生壳层不能在这里自更 (iOS 走商店/TestFlight, Android 侧载需允许未知来源)。
|
|
1705
|
+
*/
|
|
1706
|
+
async function openUpdatePage() {
|
|
1707
|
+
const page = document.createElement('div');
|
|
1708
|
+
page.className = 'chat-page';
|
|
1709
|
+
page.id = 'update-page';
|
|
1710
|
+
page.innerHTML = `
|
|
1711
|
+
<div class="chat-topbar">
|
|
1712
|
+
<button class="icon-btn" id="update-back">←</button>
|
|
1713
|
+
<div style="flex:1;font-weight:600">App 更新</div>
|
|
1714
|
+
<button class="icon-btn" id="update-check" title="检查更新">↻</button>
|
|
1715
|
+
</div>
|
|
1716
|
+
<div style="padding:12px;overflow:auto">
|
|
1717
|
+
<pre id="update-identity" style="white-space:pre-wrap;word-break:break-all;font-size:12px;line-height:1.6;background:var(--bg-2,#0001);padding:10px;border-radius:10px;margin:0 0 10px">读取中…</pre>
|
|
1718
|
+
<div style="display:flex;gap:8px;margin-bottom:10px">
|
|
1719
|
+
<button class="conv-item" id="update-ch-stable" style="flex:1;justify-content:center">stable (npm)</button>
|
|
1720
|
+
<button class="conv-item" id="update-ch-dev" style="flex:1;justify-content:center">dev (GitHub master)</button>
|
|
1721
|
+
</div>
|
|
1722
|
+
<div class="conv-item" id="update-apply"><span class="list-icon">${ICONS.chip}</span><span style="flex:1;min-width:0"><span style="display:block">安装更新(需要你确认)</span><span class="conv-preview" style="display:block" id="update-apply-sub">先点右上角 ↻ 检查</span></span><span class="list-arrow">›</span></div>
|
|
1723
|
+
<div class="conv-item" id="update-rollback"><span class="list-icon">${ICONS.trash}</span><span style="flex:1;min-width:0"><span style="display:block">回滚到上一份</span><span class="conv-preview" style="display:block">装坏了就换回上一次能用的资源</span></span><span class="list-arrow">›</span></div>
|
|
1724
|
+
<div class="conv-item" id="update-reload"><span class="list-icon">${ICONS.themeAuto}</span><span>重载界面让更新生效</span></div>
|
|
1725
|
+
<div id="update-ceiling" style="font-size:12px;line-height:1.6;color:var(--fg-2,#888);margin:10px 0"></div>
|
|
1726
|
+
<pre id="update-log" style="white-space:pre-wrap;word-break:break-all;font-size:12px;line-height:1.6;margin:0"></pre>
|
|
1727
|
+
</div>`;
|
|
1728
|
+
document.body.appendChild(page);
|
|
1729
|
+
|
|
1730
|
+
const identityEl = page.querySelector('#update-identity');
|
|
1731
|
+
const logEl = page.querySelector('#update-log');
|
|
1732
|
+
const applySub = page.querySelector('#update-apply-sub');
|
|
1733
|
+
const ceilingEl = page.querySelector('#update-ceiling');
|
|
1734
|
+
let lastCheck = null;
|
|
1735
|
+
|
|
1736
|
+
const log = (line) => { logEl.textContent += `${line}\n`; logEl.scrollTop = logEl.scrollHeight; };
|
|
1737
|
+
const UP = () => (window.BolloonCore && window.BolloonCore.update) || null;
|
|
1738
|
+
const setIdentity = async () => {
|
|
1739
|
+
const up = UP();
|
|
1740
|
+
if (!up) { identityEl.textContent = '内核未就绪'; return; }
|
|
1741
|
+
const voc = up.vocabulary();
|
|
1742
|
+
ceilingEl.textContent = voc.nativeCeiling;
|
|
1743
|
+
const st = await up.state();
|
|
1744
|
+
const applied = st.installedChannel ? `${st.installedChannel}${st.installedDevSha ? ` @ ${st.installedDevSha}` : ''}` : '(还没用这个功能装过)';
|
|
1745
|
+
identityEl.textContent = [
|
|
1746
|
+
`当前装的: ${applied}`,
|
|
1747
|
+
`通道偏好: ${up.channel()}`,
|
|
1748
|
+
`能切回: ${st.switchableTo ? `${st.switchableTo.channel} (${st.switchableTo.source}${st.switchableTo.target ? ` @ ${st.switchableTo.target}` : ''})` : '—'}`,
|
|
1749
|
+
`上次来源: ${st.installedFrom ? `${st.installedFrom.source}${st.installedFrom.digest ? ` · ${st.installedFrom.digest}` : ''}` : '—'}`,
|
|
1750
|
+
`更新层: web 资源层(界面与逻辑:入群 / 发任务公告 / 看飞轮进度 / 授权签名)`,
|
|
1751
|
+
`会生效吗: ${up.nativeWired() ? '会(原生壳已指向可写目录)' : '不会 —— 原生壳还没指向可写目录,装了也不生效,所以不会切换'}`,
|
|
1752
|
+
].join('\n');
|
|
1753
|
+
};
|
|
1754
|
+
const runCheck = async () => {
|
|
1755
|
+
const up = UP();
|
|
1756
|
+
if (!up) { log('内核未就绪'); return; }
|
|
1757
|
+
log('· 检查两源(npm + GitHub)…');
|
|
1758
|
+
const r = await up.report({});
|
|
1759
|
+
lastCheck = r.result;
|
|
1760
|
+
identityEl.textContent = r.lines.join('\n');
|
|
1761
|
+
applySub.textContent = r.result.status === 'update_available'
|
|
1762
|
+
? `可装: ${r.result.targetIdentity || r.result.latestVersion}` : `结论: ${r.result.status}`;
|
|
1763
|
+
log(`· 结论: ${r.result.status}${r.result.reason ? ` — ${r.result.reason}` : ''}`);
|
|
1764
|
+
if (r.result.warnings && r.result.warnings.length) r.result.warnings.forEach((w) => log(` ⚠ ${w}`));
|
|
1765
|
+
await setIdentity();
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
page.querySelector('#update-back').addEventListener('click', () => { page.remove(); void openSettings(); });
|
|
1769
|
+
page.querySelector('#update-check').addEventListener('click', () => { logEl.textContent = ''; void runCheck(); });
|
|
1770
|
+
page.querySelector('#update-ch-stable').addEventListener('click', async () => { const r = await UP().setChannel('stable'); log(`· ${r.detail}`); await setIdentity(); });
|
|
1771
|
+
page.querySelector('#update-ch-dev').addEventListener('click', async () => { const r = await UP().setChannel('dev'); log(`· ${r.detail}`); await setIdentity(); });
|
|
1772
|
+
page.querySelector('#update-apply').addEventListener('click', async () => {
|
|
1773
|
+
const up = UP();
|
|
1774
|
+
if (!up) return;
|
|
1775
|
+
const target = lastCheck && (lastCheck.targetIdentity || lastCheck.latestVersion);
|
|
1776
|
+
if (!lastCheck || lastCheck.status !== 'update_available') { log('· 先检查更新(右上角 ↻):现在没有可装的目标,或源不可达/交叉校验不一致 —— 拒绝安装。'); return; }
|
|
1777
|
+
// 人在环: 说清要换的是哪一层, 再动手
|
|
1778
|
+
if (!confirm(`要装 ${target} 吗?\n\n换的是「web 资源层」(界面与逻辑)。装完需要重载界面才生效;装坏了可以一键回滚。\n原生壳层(二进制)不在这里更新。`)) return;
|
|
1779
|
+
log('· 准备并验证 …');
|
|
1780
|
+
const prep = await up.prepare({ onStage: (s, d) => log(` [${s}] ${d}`) });
|
|
1781
|
+
if (!prep.ok) { log(`· 准备/验证失败: ${prep.status} — ${prep.reason}`); return; }
|
|
1782
|
+
log(`· 已验证 ${prep.to}(${prep.fileCount} 个文件)—— 现在切换`);
|
|
1783
|
+
const out = await up.apply({ confirm: true, reuseStaging: true, onStage: (s, d) => log(` [${s}] ${d}`) });
|
|
1784
|
+
log(out.ok ? `· 成功: ${out.from} → ${out.to}(重载界面后生效)` : `· 未生效 (${out.stage}): ${out.reason}`);
|
|
1785
|
+
applySub.textContent = out.ok ? '已装好 · 点「重载界面」生效' : `未生效: ${out.status}`;
|
|
1786
|
+
await setIdentity();
|
|
1787
|
+
});
|
|
1788
|
+
page.querySelector('#update-rollback').addEventListener('click', async () => {
|
|
1789
|
+
const up = UP();
|
|
1790
|
+
if (!up) return;
|
|
1791
|
+
if (!confirm('换回上一份能用的 web 资源吗?(当前这一份会被替换掉)')) return;
|
|
1792
|
+
const out = await up.rollback({ confirm: true, onStage: (s, d) => log(` [${s}] ${d}`) });
|
|
1793
|
+
log(out.ok ? `· 已回滚到 ${out.to}` : `· 回滚未执行 (${out.stage}): ${out.reason}`);
|
|
1794
|
+
await setIdentity();
|
|
1795
|
+
});
|
|
1796
|
+
page.querySelector('#update-reload').addEventListener('click', async () => {
|
|
1797
|
+
const r = await UP().reload();
|
|
1798
|
+
log(`· ${r.detail}(方式: ${r.how})`);
|
|
1799
|
+
});
|
|
1800
|
+
|
|
1801
|
+
await setIdentity();
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1700
1804
|
function openSettings() {
|
|
1701
1805
|
const page = document.createElement('div');
|
|
1702
1806
|
page.className = 'chat-page';
|
|
@@ -1709,6 +1813,7 @@
|
|
|
1709
1813
|
<div style="padding:12px">
|
|
1710
1814
|
<div class="conv-item" id="api-config-item"><span class="list-icon">${ICONS.chip}</span><span>API 配置</span><span class="list-arrow">›</span></div>
|
|
1711
1815
|
<div class="conv-item" id="theme-toggle"><span class="list-icon" id="theme-icon">${ICONS.themeAuto}</span><span id="theme-text">跟随系统</span></div>
|
|
1816
|
+
<div class="conv-item" id="settings-update"><span class="list-icon">${ICONS.themeAuto}</span><span style="flex:1;min-width:0"><span style="display:block">App 更新(web 资源层)</span><span class="conv-preview" style="display:block" id="update-preview">读取中…</span></span><span class="list-arrow">›</span></div>
|
|
1712
1817
|
<div class="conv-item" id="settings-data"><span class="list-icon">${ICONS.chip}</span><span style="flex:1;min-width:0"><span style="display:block">本机数据</span><span class="conv-preview" style="display:block">读取中…</span></span><span class="list-arrow">›</span></div>
|
|
1713
1818
|
<div class="conv-item" id="settings-desktop"><span class="list-icon">${ICONS.globe}</span><span>电脑端同步</span><span class="list-arrow">›</span></div>
|
|
1714
1819
|
<div class="conv-item" id="settings-chain"><span class="list-icon">${ICONS.chip}</span><span>链上配置 (RPC/网络)</span><span class="list-arrow">›</span></div>
|
|
@@ -1725,6 +1830,22 @@
|
|
|
1725
1830
|
applyTheme(resolveThemePref(), false);
|
|
1726
1831
|
$('#settings-back').addEventListener('click', () => page.remove());
|
|
1727
1832
|
$('#api-config-item').addEventListener('click', openApiConfig);
|
|
1833
|
+
// App 更新: 预览里就说清"现在装的是哪个源 / 哪个版本·sha / 能切回谁"(= 桌面 update --status 同一口径)
|
|
1834
|
+
$('#settings-update').addEventListener('click', () => { page.remove(); void openUpdatePage(); });
|
|
1835
|
+
void (async () => {
|
|
1836
|
+
const el = page.querySelector('#update-preview');
|
|
1837
|
+
if (!el) return;
|
|
1838
|
+
try {
|
|
1839
|
+
const up = window.BolloonCore && window.BolloonCore.update;
|
|
1840
|
+
if (!up) { el.textContent = '内核未就绪'; return; }
|
|
1841
|
+
const st = await up.state();
|
|
1842
|
+
const applied = st.installedChannel
|
|
1843
|
+
? `${st.installedChannel}${st.installedDevSha ? ` @ ${st.installedDevSha}` : ''}`
|
|
1844
|
+
: '内置资源';
|
|
1845
|
+
const back = st.switchableTo ? ` · 能切回 ${st.switchableTo.channel}` : '';
|
|
1846
|
+
el.textContent = `当前 ${applied}${back} · 通道 ${up.channel()}${up.nativeWired() ? '' : ' · 原生壳未接通'}`;
|
|
1847
|
+
} catch (e) { el.textContent = '读取失败: ' + ((e && e.message) || e); }
|
|
1848
|
+
})();
|
|
1728
1849
|
$('#theme-toggle').addEventListener('click', () => {
|
|
1729
1850
|
const next = currentTheme === 'auto' ? 'light' : (currentTheme === 'light' ? 'dark' : 'auto');
|
|
1730
1851
|
applyTheme(next, true);
|
|
@@ -1827,9 +1948,28 @@
|
|
|
1827
1948
|
$('#settings-did').addEventListener('click', () => { api.get('/api/auth/status').then((s) => alert('DID: ' + (s.did || '未生成'))); });
|
|
1828
1949
|
}
|
|
1829
1950
|
|
|
1830
|
-
// ===
|
|
1831
|
-
//
|
|
1832
|
-
//
|
|
1951
|
+
// === 模型配置 (供应商 / 模型 / 地址) ===
|
|
1952
|
+
//
|
|
1953
|
+
// 2026-09-26 (模型链路升级 P6 的手机侧): 这一页**只显示与触发**, 自己不算配置 ——
|
|
1954
|
+
// 唯一事实来自桌面端的模型端点 (与 CLI `/model` / Web 桌面页**同一份**):
|
|
1955
|
+
//
|
|
1956
|
+
// 当前真实生效 GET /api/models/providers → body.effective
|
|
1957
|
+
// (provider · model · baseUrl · scope · configHash)
|
|
1958
|
+
// 目录 + 来源 GET /api/models/options?provider=<id> → body.catalog.origin
|
|
1959
|
+
// (live | cached | curated | custom | unavailable)
|
|
1960
|
+
// 刷新发现 POST /api/models/discover {action:'refresh', provider}
|
|
1961
|
+
// 手输模型 POST /api/models/discover {action:'admit', provider, model}
|
|
1962
|
+
//
|
|
1963
|
+
// 纪律: **不回读 /api/llm-config 自己拼 activeProvider/providers 来显示"生效配置"** —— 那是
|
|
1964
|
+
// 第二套读配置逻辑, 与服务端早晚漂移。下面 `localCfg` 只喂"编辑表单"和"保存"(写路径),
|
|
1965
|
+
// 展示层一个字都不从它取; 拿不到桌面端就如实说"显示不了", 不猜不编。
|
|
1966
|
+
//
|
|
1967
|
+
// 注意: 手机端 RemoteLlm 走 OpenAI 兼容协议 (baseUrl + /chat/completions) —— 自定义 provider
|
|
1968
|
+
// 必须是 OpenAI 兼容端点 (gemini 用 /v1beta/openai, 智谱 v4 / dashscope compatible-mode 都兼容)。
|
|
1969
|
+
const ORIGIN_LABEL = {
|
|
1970
|
+
live: '实时发现 (live)', cached: '缓存发现 (cached)', curated: '内置清单 (curated)',
|
|
1971
|
+
custom: '自定义 (custom)', unavailable: '不可用 (unavailable)',
|
|
1972
|
+
};
|
|
1833
1973
|
const LLM_PROVIDERS = [
|
|
1834
1974
|
{ id: 'deepseek', label: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
|
|
1835
1975
|
{ id: 'openai', label: 'OpenAI', baseUrl: 'https://api.openai.com/v1', model: 'gpt-4o-mini' },
|
|
@@ -1847,43 +1987,155 @@
|
|
|
1847
1987
|
{ id: 'custom', label: '自定义', baseUrl: '', model: '' },
|
|
1848
1988
|
];
|
|
1849
1989
|
const LLM_BY_ID = LLM_PROVIDERS.reduce((m, p) => (m[p.id] = p, m), {});
|
|
1990
|
+
|
|
1991
|
+
/** configHash 只显示前 8 位 (完整值在桌面端 / Run 快照里, 手机屏不需要 64 位) */
|
|
1992
|
+
function shortConfigHash(h) {
|
|
1993
|
+
const s = String(h == null ? '' : h);
|
|
1994
|
+
return s ? s.slice(0, 8) : '—';
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
/** 目录来源标记 → 人话 (认不出来就原样回显, 不编) */
|
|
1998
|
+
function originLabel(origin) {
|
|
1999
|
+
const k = String(origin || '');
|
|
2000
|
+
return ORIGIN_LABEL[k] || (k ? k : '—');
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
/** 电脑端基址 (设置里填; 手机自足模式拿不到就如实说, 不假装成功) */
|
|
2004
|
+
async function modelApiGet(path) {
|
|
2005
|
+
const base = await desktopBaseUrl();
|
|
2006
|
+
if (!base) return { ok: false, error: '未接电脑端 —— 设置 → 电脑端同步 里填桌面地址后可看' };
|
|
2007
|
+
const r = await desktopFetch(path);
|
|
2008
|
+
if (r === null) return { ok: false, error: '电脑端不可达或返回非 200' };
|
|
2009
|
+
return { ok: true, data: r };
|
|
2010
|
+
}
|
|
2011
|
+
async function modelApiPost(path, body) {
|
|
2012
|
+
const base = await desktopBaseUrl();
|
|
2013
|
+
if (!base) return { ok: false, error: '未接电脑端 —— 设置 → 电脑端同步 里填桌面地址后可看' };
|
|
2014
|
+
return await desktopPostRaw(path, body); // 保留后端错误原文
|
|
2015
|
+
}
|
|
2016
|
+
|
|
1850
2017
|
async function openApiConfig() {
|
|
1851
|
-
let cfg;
|
|
1852
|
-
try { cfg = await api.get('/api/llm-config'); } catch { cfg = null; }
|
|
1853
|
-
if (!cfg || !cfg.providers) cfg = { activeProvider: 'deepseek', providers: {}, updatedAt: Date.now() };
|
|
1854
2018
|
const page = document.createElement('div');
|
|
1855
2019
|
page.className = 'chat-page';
|
|
1856
2020
|
page.id = 'api-config-page';
|
|
1857
|
-
|
|
1858
|
-
|
|
2021
|
+
// 编辑表单的本地草稿 (写路径) —— 展示层不读它
|
|
2022
|
+
let localCfg = null;
|
|
2023
|
+
try { localCfg = await api.get('/api/llm-config'); } catch (e) { localCfg = null; }
|
|
2024
|
+
if (!localCfg || !localCfg.providers) localCfg = { activeProvider: 'deepseek', providers: {}, updatedAt: Date.now() };
|
|
2025
|
+
const _in = 'width:100%;padding:10px;border:1px solid var(--border);border-radius:8px;background:var(--bg-hover);color:var(--text);box-sizing:border-box';
|
|
2026
|
+
const _btn = 'padding:10px 14px;border:1px solid var(--border);border-radius:8px;background:var(--bg-hover);color:var(--text)';
|
|
1859
2027
|
page.innerHTML = `
|
|
1860
2028
|
<div class="chat-topbar">
|
|
1861
2029
|
<button class="icon-btn" id="api-config-back">←</button>
|
|
1862
|
-
<div style="flex:1;font-weight:600"
|
|
2030
|
+
<div style="flex:1;font-weight:600">模型配置</div>
|
|
1863
2031
|
</div>
|
|
1864
|
-
<div style="padding:12px;display:flex;flex-direction:column;gap:12px">
|
|
1865
|
-
<
|
|
1866
|
-
<div
|
|
1867
|
-
|
|
2032
|
+
<div style="padding:12px;display:flex;flex-direction:column;gap:12px;overflow:auto">
|
|
2033
|
+
<div class="section-label">当前真实生效 (电脑端 /api/models/providers)</div>
|
|
2034
|
+
<div id="api-eff-card" style="padding:12px;border:1px solid var(--border);border-radius:10px;background:var(--bg-card);display:flex;flex-direction:column;gap:6px">
|
|
2035
|
+
<div style="font-size:13px">供应商: <b id="api-eff-provider">读取中…</b></div>
|
|
2036
|
+
<div style="font-size:13px">模型: <b id="api-eff-model">—</b></div>
|
|
2037
|
+
<div style="font-size:12px;color:var(--text-secondary);word-break:break-all">地址: <span id="api-eff-baseurl">—</span></div>
|
|
2038
|
+
<div style="font-size:12px;color:var(--text-secondary)">来源(scope): <span id="api-eff-scope">—</span> · 配置指纹: <span id="api-eff-hash">—</span></div>
|
|
2039
|
+
<div id="api-eff-note" style="font-size:12px;color:var(--text-muted)"></div>
|
|
2040
|
+
</div>
|
|
2041
|
+
|
|
2042
|
+
<div class="section-label">模型目录 (电脑端 /api/models/options)</div>
|
|
2043
|
+
<div id="api-catalog-origin" style="font-size:13px">—</div>
|
|
2044
|
+
<div id="api-catalog-head" style="font-size:12px;color:var(--text-secondary)"></div>
|
|
2045
|
+
<div id="api-catalog-list" style="font-size:12px;color:var(--text-secondary);max-height:170px;overflow:auto;word-break:break-all"></div>
|
|
2046
|
+
<div id="api-catalog-note" style="font-size:12px;color:var(--text-muted)"></div>
|
|
2047
|
+
<button id="api-discover-refresh" style="${_btn}">刷新发现</button>
|
|
2048
|
+
<div style="display:flex;gap:8px">
|
|
2049
|
+
<input id="api-manual-model" placeholder="手输模型名 (目录里没有的)" style="flex:1;${_in}">
|
|
2050
|
+
<button id="api-manual-admit" style="${_btn}">加入目录</button>
|
|
1868
2051
|
</div>
|
|
2052
|
+
<div id="api-discover-result" style="font-size:12px;color:var(--text-secondary)"></div>
|
|
2053
|
+
|
|
2054
|
+
<div class="section-label">供应商 / 端点 / 凭据 (本机保存) <span id="api-provider-count" style="opacity:.55"></span></div>
|
|
2055
|
+
<div class="provider-chips" id="api-provider-chips"></div>
|
|
1869
2056
|
<div id="api-hint" style="font-size:12px;color:var(--text-muted)"></div>
|
|
1870
|
-
<
|
|
1871
|
-
<input id="api-
|
|
1872
|
-
<
|
|
1873
|
-
<input id="api-key" type="password" placeholder="sk-..." value="${escapeHtml(pc.apiKey || '')}" style="padding:10px;border:1px solid var(--border);border-radius:8px;background:var(--bg-hover);color:var(--text)">
|
|
1874
|
-
<label style="font-size:13px;color:var(--text-secondary)">模型</label>
|
|
1875
|
-
<input id="api-model" placeholder="模型名" value="${escapeHtml(pc.model || '')}" style="padding:10px;border:1px solid var(--border);border-radius:8px;background:var(--bg-hover);color:var(--text)">
|
|
2057
|
+
<input id="api-baseurl" placeholder="https://api.xxx.com/v1" style="${_in}">
|
|
2058
|
+
<input id="api-key" type="password" placeholder="sk-..." style="${_in}">
|
|
2059
|
+
<input id="api-model" placeholder="模型名" style="${_in}">
|
|
1876
2060
|
<button id="api-save" style="padding:12px;border:none;border-radius:10px;background:var(--accent);color:var(--bg);font-weight:700">保存配置</button>
|
|
1877
2061
|
</div>`;
|
|
1878
2062
|
document.body.appendChild(page);
|
|
1879
2063
|
$('#api-config-back').addEventListener('click', () => page.remove());
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2064
|
+
const setText = (sel, v) => { const el = page.querySelector(sel); if (el) el.textContent = String(v == null ? '' : v); };
|
|
2065
|
+
|
|
2066
|
+
// ── ① 当前真实生效: 只认服务端 effective ──────────────────────────
|
|
2067
|
+
let lastProviders = null;
|
|
2068
|
+
async function paintEffective() {
|
|
2069
|
+
const r = await modelApiGet('/api/models/providers');
|
|
2070
|
+
if (!r.ok) {
|
|
2071
|
+
setText('#api-eff-provider', '—'); setText('#api-eff-model', '—');
|
|
2072
|
+
setText('#api-eff-baseurl', '—'); setText('#api-eff-scope', '—'); setText('#api-eff-hash', '—');
|
|
2073
|
+
setText('#api-eff-note', '读不到生效配置: ' + r.error);
|
|
2074
|
+
return null;
|
|
2075
|
+
}
|
|
2076
|
+
const eff = r.data && r.data.effective;
|
|
2077
|
+
if (!eff) {
|
|
2078
|
+
setText('#api-eff-note', '电脑端没给出 effective (不猜: 不显示任何默认值)');
|
|
2079
|
+
return r.data;
|
|
2080
|
+
}
|
|
2081
|
+
setText('#api-eff-provider', eff.provider);
|
|
2082
|
+
setText('#api-eff-model', eff.model || '—');
|
|
2083
|
+
setText('#api-eff-baseurl', eff.baseUrl || '—');
|
|
2084
|
+
setText('#api-eff-scope', eff.scope || eff.source || '—');
|
|
2085
|
+
setText('#api-eff-hash', shortConfigHash(eff.configHash));
|
|
2086
|
+
setText('#api-eff-note', '协议 ' + (eff.protocol || '?') + ' · 凭据 ' + (eff.authRef || 'none')
|
|
2087
|
+
+ ' · 指纹前 8 位 (完整值见电脑端 Run 快照)');
|
|
2088
|
+
return r.data;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
// ── ② 目录 + 来源标记 ────────────────────────────────────────────
|
|
2092
|
+
let picked = (localCfg.activeProvider && LLM_BY_ID[localCfg.activeProvider]) ? localCfg.activeProvider : 'deepseek';
|
|
2093
|
+
async function paintCatalog() {
|
|
2094
|
+
setText('#api-catalog-origin', '来源: 读取中…');
|
|
2095
|
+
setText('#api-catalog-head', ''); setText('#api-catalog-list', ''); setText('#api-catalog-note', '');
|
|
2096
|
+
const r = await modelApiGet('/api/models/options?provider=' + encodeURIComponent(picked));
|
|
2097
|
+
if (!r.ok) { setText('#api-catalog-origin', '来源: —'); setText('#api-catalog-note', '读不到目录: ' + r.error); return; }
|
|
2098
|
+
const d = r.data || {};
|
|
2099
|
+
const cat = d.catalog || null;
|
|
2100
|
+
setText('#api-catalog-origin', '来源: ' + originLabel(cat && cat.origin));
|
|
2101
|
+
const models = Array.isArray(d.models) ? d.models : [];
|
|
2102
|
+
setText('#api-catalog-head', String(d.count == null ? models.length : d.count) + ' 个模型'
|
|
2103
|
+
+ (cat && cat.stale ? ' · 缓存已过期' : '')
|
|
2104
|
+
+ (cat && cat.discoveryFailed ? ' · 上游发现失败' : ''));
|
|
2105
|
+
const listEl = page.querySelector('#api-catalog-list');
|
|
2106
|
+
if (listEl) {
|
|
2107
|
+
listEl.innerHTML = models.slice(0, 40).map((m) => {
|
|
2108
|
+
const id = escapeHtml(String((m && (m.id || m.name)) || ''));
|
|
2109
|
+
const tool = (m && m.toolCalling) ? ' · 工具调用 ' + escapeHtml(String(m.toolCalling)) : '';
|
|
2110
|
+
return '<div>' + id + tool + '</div>';
|
|
2111
|
+
}).join('') || '(空目录)';
|
|
2112
|
+
}
|
|
2113
|
+
if (cat && cat.failure) {
|
|
2114
|
+
const f = cat.failure;
|
|
2115
|
+
setText('#api-catalog-note', '上游失败类目: ' + String(f.failureClass || f.class || f.message || JSON.stringify(f)).slice(0, 160));
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
// ── ③ 供应商芯片 (名字/可用性来自服务端 providers, 不是本机硬编码表) ──
|
|
2120
|
+
function paintChips() {
|
|
2121
|
+
const box = page.querySelector('#api-provider-chips');
|
|
2122
|
+
if (!box) return;
|
|
2123
|
+
const rows = (lastProviders && Array.isArray(lastProviders.providers) && lastProviders.providers.length)
|
|
2124
|
+
? lastProviders.providers.map((p) => ({ id: String(p.id), label: String(p.name || p.id), configured: p.configured !== false, current: p.current === true, isLocal: p.isLocal === true }))
|
|
2125
|
+
: LLM_PROVIDERS;
|
|
2126
|
+
box.innerHTML = rows.map((p) => `<button type="button" class="provider-chip${p.id === picked ? ' active' : ''}" data-provider="${escapeHtml(p.id)}">${escapeHtml(p.label)}</button>`).join('');
|
|
2127
|
+
const cn = page.querySelector('#api-provider-count');
|
|
2128
|
+
if (cn) {
|
|
2129
|
+
const ready = rows.filter((p) => p.configured !== false).length;
|
|
2130
|
+
cn.textContent = '(' + ready + ' / ' + rows.length + ' 家可用凭据就绪)';
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
function fillFrom(id) {
|
|
2134
|
+
const saved = (localCfg.providers && localCfg.providers[id]) || {};
|
|
1884
2135
|
const d = LLM_BY_ID[id] || {};
|
|
1885
|
-
|
|
1886
|
-
$('#api-
|
|
2136
|
+
const row = lastProviders && Array.isArray(lastProviders.providers) ? lastProviders.providers.find((x) => String(x.id) === String(id)) : null;
|
|
2137
|
+
$('#api-baseurl').value = saved.baseUrl || (row && row.baseUrl) || d.baseUrl || '';
|
|
2138
|
+
$('#api-model').value = saved.model || (row && row.configuredModel) || d.model || '';
|
|
1887
2139
|
$('#api-key').value = saved.apiKey || '';
|
|
1888
2140
|
const hint = $('#api-hint');
|
|
1889
2141
|
if (hint) {
|
|
@@ -1892,23 +2144,37 @@
|
|
|
1892
2144
|
: (saved.apiKey ? '该供应商已保存过 key(改完记得再点保存)'
|
|
1893
2145
|
: '填官方文档里的 API key; 保存后本机智能体就走它');
|
|
1894
2146
|
}
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
$$('#api-provider-chips .provider-chip').forEach((c) => c.classList.toggle('active', c.dataset.provider === picked));
|
|
1898
|
-
};
|
|
1899
|
-
const configured = Object.keys(cfg.providers || {}).filter((k) => cfg.providers[k] && cfg.providers[k].apiKey);
|
|
1900
|
-
$('#api-provider-count').textContent = configured.length ? `(已配 ${configured.length} 个: ${configured.join(' / ')})` : '';
|
|
1901
|
-
$('#api-provider-chips').addEventListener('click', (e) => {
|
|
2147
|
+
}
|
|
2148
|
+
page.querySelector('#api-provider-chips').addEventListener('click', (e) => {
|
|
1902
2149
|
const b = e.target.closest && e.target.closest('.provider-chip');
|
|
1903
2150
|
if (!b) return;
|
|
1904
2151
|
picked = b.dataset.provider;
|
|
1905
2152
|
paintChips();
|
|
1906
2153
|
fillFrom(picked);
|
|
2154
|
+
void paintCatalog();
|
|
1907
2155
|
});
|
|
1908
|
-
|
|
2156
|
+
|
|
2157
|
+
// ── ④ 刷新发现 / ⑤ 手输模型 (都打在服务端唯一入口上) ─────────────
|
|
2158
|
+
page.querySelector('#api-discover-refresh').addEventListener('click', async () => {
|
|
2159
|
+
setText('#api-discover-result', '刷新中…');
|
|
2160
|
+
const r = await modelApiPost('/api/models/discover', { action: 'refresh', provider: picked, force: true });
|
|
2161
|
+
if (r && r.ok === false) { setText('#api-discover-result', '刷新失败: ' + String(r.error || r.message || '').slice(0, 200)); return; }
|
|
2162
|
+
setText('#api-discover-result', '刷新完成 · 来源 ' + originLabel(r && r.catalog && r.catalog.origin));
|
|
2163
|
+
await paintCatalog();
|
|
2164
|
+
});
|
|
2165
|
+
page.querySelector('#api-manual-admit').addEventListener('click', async () => {
|
|
2166
|
+
const model = String($('#api-manual-model').value || '').trim();
|
|
2167
|
+
if (!model) { setText('#api-discover-result', '先填一个模型名'); return; }
|
|
2168
|
+
const r = await modelApiPost('/api/models/discover', { action: 'admit', provider: picked, model });
|
|
2169
|
+
if (r && r.ok === false) { setText('#api-discover-result', '加入失败: ' + String(r.error || r.message || '').slice(0, 200)); return; }
|
|
2170
|
+
setText('#api-discover-result', '已加入目录: ' + model);
|
|
2171
|
+
await paintCatalog();
|
|
2172
|
+
});
|
|
2173
|
+
|
|
2174
|
+
// ── ⑥ 编辑表单保存 (写路径, 与从前同一口) ─────────────────────────
|
|
1909
2175
|
$('#api-save').addEventListener('click', async () => {
|
|
1910
2176
|
const p = picked;
|
|
1911
|
-
const next = (
|
|
2177
|
+
const next = (localCfg && localCfg.providers) ? localCfg : { activeProvider: localCfg.activeProvider, providers: {}, updatedAt: Date.now() };
|
|
1912
2178
|
next.activeProvider = p;
|
|
1913
2179
|
next.providers[p] = Object.assign({}, (next.providers[p] || {}), {
|
|
1914
2180
|
enabled: true, apiKey: $('#api-key').value.trim(), baseUrl: $('#api-baseurl').value.trim(),
|
|
@@ -1920,6 +2186,12 @@
|
|
|
1920
2186
|
page.remove();
|
|
1921
2187
|
} catch (e) { alert('保存失败: ' + (e.message || e)); }
|
|
1922
2188
|
});
|
|
2189
|
+
|
|
2190
|
+
// 启动: 先画芯片(服务端清单) → 再画生效卡片与目录
|
|
2191
|
+
lastProviders = await paintEffective();
|
|
2192
|
+
paintChips();
|
|
2193
|
+
fillFrom(picked);
|
|
2194
|
+
await paintCatalog();
|
|
1923
2195
|
}
|
|
1924
2196
|
|
|
1925
2197
|
// === 加密钱包 (只读 MVP) ===
|
|
@@ -3693,7 +3965,8 @@
|
|
|
3693
3965
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bindEvents);
|
|
3694
3966
|
else bindEvents();
|
|
3695
3967
|
|
|
3696
|
-
//
|
|
3968
|
+
// 供验收脚本/壳层驱动 (真 headless DOM 用): 只暴露行为入口, 不暴露数据
|
|
3969
|
+
window.__mobileUpdateUi = { open: () => openUpdatePage(), openSettings: () => openSettings() };
|
|
3697
3970
|
window.__mobileTasksUi = {
|
|
3698
3971
|
refresh: refreshAll, askJoin: askJoin, askCreate: askCreate, askPublish: askPublish,
|
|
3699
3972
|
askTrail: askTrail, askAnnounceToGroup: askAnnounceToGroup, confirm: doConfirmed,
|