sintbot 1.4.3 → 1.4.5
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/CHANGELOG.en.json +62 -0
- package/CHANGELOG.json +62 -0
- package/docs/AI-ASSISTED-INSTALL.md +2 -2
- package/docs/install.md +10 -7
- package/docs/zh-CN/AI-ASSISTED-INSTALL.md +2 -2
- package/docs/zh-CN/install.md +10 -7
- package/install.sh +1 -1
- package/package.json +1 -1
- package/src/agent/codexstatus.js +130 -5
- package/src/c2configcontrol.js +86 -10
- package/src/c2console.js +52 -4
- package/src/core/v2engine.js +148 -14
- package/src/ui/console.html +329 -24
- package/src/ui/serverbase.js +74 -19
package/src/ui/console.html
CHANGED
|
@@ -743,6 +743,32 @@ h2.sec{font-family:var(--sans);font-size:14px;letter-spacing:.2px;color:var(--in
|
|
|
743
743
|
.modal-bg.show{display:flex}
|
|
744
744
|
.modal{width:min(520px,92vw);background:var(--modal-bg);border:1px solid var(--line-hi);border-radius:16px;padding:26px;max-height:82vh;overflow:auto}
|
|
745
745
|
.modal h3{font-family:var(--sans);font-size:15px;font-weight:600;margin:0 0 18px;letter-spacing:.4px}
|
|
746
|
+
/* ── 控制台升级:确认 + 进度弹窗 ── 每一步的状态只从升级任务记录推出来,重载、断线重连后画出同一张 */
|
|
747
|
+
.modal.upg h3{display:flex;align-items:center;gap:10px;margin-bottom:6px}
|
|
748
|
+
.upg-sub{font-size:12px;color:var(--dim);margin:0 0 16px;display:flex;gap:10px;flex-wrap:wrap}
|
|
749
|
+
.upg-bar{height:4px;border-radius:4px;background:var(--line);overflow:hidden;margin:0 0 14px;position:relative}
|
|
750
|
+
.upg-bar>i{position:absolute;inset:0 auto 0 0;border-radius:4px;background:var(--ember);transition:width .5s var(--ease,ease)}
|
|
751
|
+
.upg-bar.run>i::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.45),transparent);animation:upg-sheen 1.3s linear infinite}
|
|
752
|
+
.upg-bar.ok>i{background:var(--ok)}.upg-bar.fail>i{background:var(--fail)}
|
|
753
|
+
@keyframes upg-sheen{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
|
|
754
|
+
.upg-steps{list-style:none;margin:0 0 12px;padding:0;display:grid;gap:2px}
|
|
755
|
+
.upg-steps li{display:flex;align-items:center;gap:10px;padding:7px 10px;border-radius:9px;font-size:13px;color:var(--faint)}
|
|
756
|
+
.upg-steps li.done{color:var(--ink)}
|
|
757
|
+
.upg-steps li.active{color:var(--ink);background:rgba(var(--ember-rgb),.08);font-weight:600}
|
|
758
|
+
.upg-steps li.fail{color:var(--fail);background:rgba(var(--fail-rgb),.08);font-weight:600}
|
|
759
|
+
.upg-steps .dot{width:16px;height:16px;flex:none;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;
|
|
760
|
+
border:1.5px solid var(--line-hi);font-size:10px;line-height:1;box-sizing:border-box}
|
|
761
|
+
.upg-steps li.done .dot{border-color:var(--ok);background:var(--ok);color:var(--modal-bg)}
|
|
762
|
+
.upg-steps li.fail .dot{border-color:var(--fail);background:var(--fail);color:var(--modal-bg)}
|
|
763
|
+
.upg-steps li.active .dot{border-color:transparent}
|
|
764
|
+
.upg-steps li.active .dot .spin{width:12px;height:12px}
|
|
765
|
+
.upg-mark{width:22px;height:22px;flex:none;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:12px;color:var(--modal-bg)}
|
|
766
|
+
.upg-mark.ok{background:var(--ok)}.upg-mark.fail{background:var(--fail)}
|
|
767
|
+
.upg-note{font-size:12.5px;line-height:1.6;color:var(--dim);padding:10px 12px;border-radius:10px;background:var(--inset-bg);margin:0 0 16px}
|
|
768
|
+
.upg-note.warn{color:var(--warn)}.upg-note.fail{color:var(--fail)}
|
|
769
|
+
.upg-btns{display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap}
|
|
770
|
+
.mark .ver-update.upgrading .update-dot{width:8px;height:8px;background:transparent;box-shadow:none;border:1.5px solid var(--spin-track);
|
|
771
|
+
border-top-color:var(--ember);animation:sp .8s linear infinite}
|
|
746
772
|
/* ── 新建/编辑自定义风格(左表单 · 右实时预览)────────────────────────────────
|
|
747
773
|
预览必须**一直在视野里** —— 这张表单的每一项都对应预览图的一条通道,改哪儿图上立刻变哪儿
|
|
748
774
|
才是它区别于「填一份 YAML」的全部价值;所以右列 sticky,滚表单时图不走。 */
|
|
@@ -1013,7 +1039,7 @@ const IC=new Proxy({},{get:(_,k)=>`<svg class="ic ic-${k}" aria-hidden="true"><u
|
|
|
1013
1039
|
const PLAT_LOGO={feishu:'data:image/png;base64,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',slack:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAiCAMAAADiW5DOAAABDlBMVEUAAAD/xgD/swD/xgD/wgDgCnDrCmxIt1hDtFrjB2o+ulrkB2tEu13mBmr5wQbkBWdCuFdBtFr6vwUAsv8Asf8AtP8Asv8As/8AtP9AuFb7vwTnB23/vgMAsv//wANBtVdBtVnmBmwAs//8wQMAtP8As//jBWsAs//jB2xDtVf6wAUAsv/lBmnmBmxDt1nmB2vkB2pBt1gAtP/9wgNCt1n8wAP8wANCtlgAs/8AtP8Atf8Atv8At/8AuP9BtlhBt1hCuFlCuVlCuVpCulpDu1pDvFtEvVxEvlzjBmrkBmrkBmvlBmvnBmzoBmzqBm3rBm7tBm/8wAP9wQP/wgP/wwP/xAP/xQP/xgP/xwP/yAN1pSH6AAAAOHRSTlMACQoSFRkaICIkJSYpKS0vMjM4PD5BQkNERERLS0xNT1ZaXmJjZWZsbW9xfn6Cio2OkZaam6qutiE9PJgAAAEnSURBVDjLrdBnV8MgFAbg1733qHvvvSWNiaMOqq2WVq3+/z8i3EgCiuegx/fLvQeehAtAlqGIRcOqmSvEN51wZZSp5ID1WKXbZUIyIUAkvnYZlkSb2GUCD7PpYXDgYXT+aHIhS5NfsMzReXzVL+sIs7JkmFOq7UBkm7xhknqbPl2ab+YCOLNJoLcKurkDxmyzDOzT1rg2bXLo6cD4y6q66Y48YPbzgpcD+MesFbmMbHZl2VMrxyJLdUouzHOemC2q28CJsDIIlLW5p1oEbCLeAK5N2nwxNeBJf56aqm3qQAftrBhm0TYNcuiuR16ehGEw82JM02S+QWZ+zq8MnXHoYcSGh3n2MMJlSkRK+g3rLtNHpgeYINPsHLq18lBpUU3ve+21MVv/AMnhmxQ0lrxLAAAAAElFTkSuQmCC',wecom:'data:image/png;base64,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'};
|
|
1014
1040
|
const platLogo=(k)=>`<img class="plat-logo" src="${PLAT_LOGO[k]}" alt="" width="18" height="18">`;
|
|
1015
1041
|
const esc=s=>String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
|
|
1016
|
-
let S=null, activity=[], usage={}, codexStatus={}; // codexStatus[name] = {loggedIn,mode,usageText} — 点「验证登录」后填,跨渲染保留
|
|
1042
|
+
let S=null, activity=[], usage={}, codexStatus={}; // codexStatus[name] = {loggedIn,mode,dead,verified,text,usageText} — 点「验证登录」后填,跨渲染保留
|
|
1017
1043
|
const pad2=n=>String(n).padStart(2,'0');
|
|
1018
1044
|
/* 活动行等时间戳统一按 bot 工作时区(S.time.offsetMin,默认北京)显示 —— 和顶栏时钟、
|
|
1019
1045
|
群内时间口径一致;state 未加载时退回浏览器本地。 */
|
|
@@ -1136,7 +1162,7 @@ function renderOverview(){
|
|
|
1136
1162
|
<span class="meta" title="${esc(g.workdir)}">${esc(shortPath(g.workdir))}</span>
|
|
1137
1163
|
${liveOn&&!g.paused?`<button class="iconbtn sm" data-osay="${esc(g.chatId)}" title="向这个群插一句 —— 空闲时马上处理;正在忙时,加入当前任务或排在它后面(看「忙时跟进」)" style="flex:none">${IC.say}</button>`:''}
|
|
1138
1164
|
${liveOn?`<button class="iconbtn sm" data-ostop="${esc(g.chatId)}" title="停止当前任务(保留会话)" style="flex:none">${IC.square}</button>
|
|
1139
|
-
<button class="iconbtn sm" data-onew="${esc(g.chatId)}" title="开新会话 — 运行中会先中断当前任务并清空队列(同群内 /new)" style="flex:none">${IC.rotate}</button>`:''}</div>`).join('')
|
|
1165
|
+
<button class="iconbtn sm" data-onew="${esc(g.chatId)}" title="${S.runtimeCapabilities?'开新会话 — 仅在该群空闲、记录全部结账后可用;运行中请先停止,结束后再开(同群内 /new)':'开新会话 — 运行中会先中断当前任务并清空队列(同群内 /new)'}" style="flex:none">${IC.rotate}</button>`:''}</div>`).join('')
|
|
1140
1166
|
:'<div class="empty">还没有群项目 · 去「群·项目」配置</div>';
|
|
1141
1167
|
$('#overview').innerHTML=`${startCardHtml()}<div id="ovInboxWrap">${inboxHtml()}</div><div class="grid cols2 stagger">
|
|
1142
1168
|
<div class="card" id="ovHealth"><div class="lbl">健康</div>
|
|
@@ -1190,7 +1216,8 @@ function renderOverview(){
|
|
|
1190
1216
|
try{const r=await api('/api/groups/stop','POST',{chatId:b.dataset.ostop});toast(S.runtimeCapabilities?(r.ok?'已请求停止,等待结束确认':'当前没有可停止的运行任务'):(r.ok?'已停止当前任务':'没有正在运行的任务'),'ok');}catch(e){toast(e.message,'err');}});
|
|
1191
1217
|
$$('#overview [data-onew]').forEach(b=>b.onclick=async()=>{
|
|
1192
1218
|
const lv=S.live&&S.live.groups&&S.live.groups[b.dataset.onew];
|
|
1193
|
-
|
|
1219
|
+
// 持久处理流程里停止要等结账确认,不能当场接着重置会话:忙时由服务端照实拒绝,不弹「中断并开新」。
|
|
1220
|
+
if(!S.runtimeCapabilities&&lv&&lv.busy&&!confirm('该群任务运行中 — 中断当前任务、清空队列并开新会话?'))return;
|
|
1194
1221
|
try{const r=await api('/api/groups/interrupt','POST',{chatId:b.dataset.onew});toast(r.wasBusy?'已中断并开新会话':'已开新会话','ok');}catch(e){toast(e.message,'err');}});
|
|
1195
1222
|
updateOverviewLive();
|
|
1196
1223
|
}
|
|
@@ -1514,7 +1541,7 @@ function makeSortable(grid,{item,key,save}){
|
|
|
1514
1541
|
c.classList.remove('dragging');drag=null;
|
|
1515
1542
|
const order=cards().map(key);
|
|
1516
1543
|
if(order.join()===startOrder.join())return; // 没动(含 Esc 后浏览器自身取消),不保存
|
|
1517
|
-
try{
|
|
1544
|
+
try{const r=await save(order);S=r;toast('顺序已保存'+runtimeChangeNote(r&&r.providerChange),'ok');}
|
|
1518
1545
|
catch(err){restore(startOrder);toast(err.message,'err');}
|
|
1519
1546
|
});
|
|
1520
1547
|
}
|
|
@@ -1577,9 +1604,10 @@ function fmtReset(w){if(!w||w.resetsAt==null)return'';const ms=w.resetsAt>1e9?(w
|
|
|
1577
1604
|
function winLabel(mins){if(!mins)return'窗口';if(mins>=1440){const d=Math.round(mins/1440);return d===7?'7d':d+'d';}return Math.round(mins/60)+'h';}
|
|
1578
1605
|
function fmtUsage(u){if(!u)return'';const f=(w)=>{if(!w)return'';const r=fmtReset(w);return `${winLabel(w.windowMins)}:${Math.min(100,Math.max(0,Math.round(w.usedPercent||0)))}%${r?` (${r})`:''}`;};return [f(u.primary),f(u.secondary)].filter(Boolean).join(' │ ');}
|
|
1579
1606
|
// 页面加载时从 /api/state 缓存回填 codex 登录态+额度 → 刷新不丢(客户端已有则不覆盖,避免盖掉刚验证的)。
|
|
1580
|
-
function seedCodexStatus(){if(!S||!S.codexStatus)return;const lg=S.codexStatus.login||{},usageText=fmtUsage(S.codexStatus.usage);(S.providers||[]).filter(p=>p.runner==='codex').forEach(p=>{if(!codexStatus[p.name])codexStatus[p.name]={loggedIn:lg.loggedIn,mode:lg.mode,usageText};});}
|
|
1607
|
+
function seedCodexStatus(){if(!S||!S.codexStatus)return;const lg=S.codexStatus.login||{},usageText=fmtUsage(S.codexStatus.usage);(S.providers||[]).filter(p=>p.runner==='codex').forEach(p=>{if(!codexStatus[p.name])codexStatus[p.name]={loggedIn:lg.loggedIn,mode:lg.mode,dead:lg.dead,verified:lg.verified,text:lg.text,usageText};});}
|
|
1581
1608
|
// codex ⚙ 面板「Codex 订阅」区的状态 HTML(登录态 + 额度),点「刷新」后就地更新。
|
|
1582
|
-
|
|
1609
|
+
// 三态(服务端文案 text 是单一事实源):已验证 / 登录态已失效(服务端拒绝令牌 ⇒ 要重新登录)/ 未验证(探不到,不等于未登录)。
|
|
1610
|
+
function cxStatusHtml(name){const c=codexStatus[name];const login=c&&c.loggedIn&&c.verified?`<span style="color:var(--ok);font-weight:600">${IC.check} ${esc(c.text||'已验证')}</span>`:(c&&c.dead?'<span style="color:var(--fail)">'+IC.x+' '+esc(c.text||'登录态已失效 — 机器上重新跑 codex login')+'</span>':(c?`<span class="faint" title="${esc(c.text||'')}">${c.loggedIn?'未验证(本机有登录文件,服务端暂时没探到)':esc(c.text||'未登录 — 机器上跑 codex login')}</span>`:'<span class="faint">未验证</span>'));return `<div>登录:${login}</div>${c&&c.usageText?`<div style="margin-top:5px">额度:<span style="color:var(--ok);font-weight:600" title="百分比 = 已用,括号 = 距重置">${esc(c.usageText)}</span></div>`:''}`;}
|
|
1583
1611
|
// 首次打开控制台、服务端无缓存时,后台自动验一次 codex 登录+额度(查询不耗消息额度,服务端缓存),卡片状态无需手动点。
|
|
1584
1612
|
// 开局自动探一次 Claude 登录态(`claude auth status --json`,0.2s、不吃订阅额度),
|
|
1585
1613
|
// 与 autoVerifyCodex 对称 —— 让卡片一进来就说真话,而不是永远挂着「未验证」等人去点。
|
|
@@ -1590,7 +1618,7 @@ async function autoVerifyClaude(){
|
|
|
1590
1618
|
if(!has)return;
|
|
1591
1619
|
try{S.claudeStatus=await api('/api/claude/verify','POST',{});renderProviders();}catch(e){}
|
|
1592
1620
|
}
|
|
1593
|
-
async function autoVerifyCodex(){const cxs=((S&&S.providers)||[]).filter(p=>p.runner==='codex'&&!codexStatus[p.name]);if(!cxs.length)return;try{const r=await api('/api/codex/verify','POST',{});const lg=r.login||{},usageText=fmtUsage(r.usage);cxs.forEach(p=>codexStatus[p.name]={loggedIn:lg.loggedIn,mode:lg.mode,usageText});renderProviders();}catch(e){}}
|
|
1621
|
+
async function autoVerifyCodex(){const cxs=((S&&S.providers)||[]).filter(p=>p.runner==='codex'&&!codexStatus[p.name]);if(!cxs.length)return;try{const r=await api('/api/codex/verify','POST',{});const lg=r.login||{},usageText=fmtUsage(r.usage);cxs.forEach(p=>codexStatus[p.name]={loggedIn:lg.loggedIn,mode:lg.mode,dead:lg.dead,verified:lg.verified,text:lg.text,usageText});renderProviders();}catch(e){}}
|
|
1594
1622
|
// Claude 订阅登录 chip:此前只能写死「登录态·未验证」(判据是「没填 baseUrl 就当登录了」),
|
|
1595
1623
|
// 真没登录也照样绿。现在有 `claude auth status --json` 这个官方非交互探针,报真状态;
|
|
1596
1624
|
// 探不到(老版本 CLI / 不在 PATH)显式说「未验证」,**不当成未登录** —— 两种情况要分得开。
|
|
@@ -1609,7 +1637,7 @@ function claudeChip(){
|
|
|
1609
1637
|
function renderProviders(){
|
|
1610
1638
|
const cards=S.providers.filter(p=>showAll||(!p.hidden&&p.configured)).map(p=>{
|
|
1611
1639
|
const cs=p.runner==='codex'?codexStatus[p.name]:null;
|
|
1612
|
-
const chip=p.runner==='codex'?(cs&&cs.loggedIn?`<span class="chip set" title="
|
|
1640
|
+
const chip=p.runner==='codex'?(cs&&cs.loggedIn&&cs.verified?`<span class="chip set" title="${esc(cs.text||'已验证')}">订阅登录 ${IC.check}</span>`:cs&&cs.dead?`<span class="chip none" title="${esc(cs.text||'')}">登录态已失效</span>`:'<span class="chip" title="点下方「验证登录」确认 codex 订阅登录态 + 拉取额度">订阅登录·未验证</span>'):p.loggedIn?claudeChip():`<span class="chip ${p.keyStatus}">${p.keyStatus==='set'?'key 已设':p.keyStatus==='env'?'env:'+esc(p.keyRef):'无 key'}</span>`;
|
|
1613
1641
|
return `<div class="card pcard${p.hidden?' is-hidden':(!p.configured?' is-unconfig':'')}" data-prov="${esc(p.name)}">
|
|
1614
1642
|
<div class="ph"><div class="pn"><span class="drag" draggable="true" title="拖动排序">${IC.grip}</span>${esc(p.name)}${p.runner==='codex'?'<span class="cli-tag codex" title="GPT 经 Codex CLI 后端 · 订阅登录">Codex CLI</span>':'<span class="cli-tag claude" title="经 Claude Code CLI · 模型大脑按 env 切换">Claude Code CLI</span>'}</div>
|
|
1615
1643
|
<div class="inline" style="gap:8px">${chip}<button class="iconbtn cfg" data-act="edit" title="配置模型">${IC.settings}</button></div></div>
|
|
@@ -1627,7 +1655,7 @@ function renderProviders(){
|
|
|
1627
1655
|
const extra=S.providers.filter(p=>p.hidden||!p.configured).length;
|
|
1628
1656
|
const toggle=extra?`<button class="btn ghost" id="showAll">${showAll?'只看已配置':`显示全部 (${extra})`}</button>`:'';
|
|
1629
1657
|
$('#providers').innerHTML=`<div class="inline" style="justify-content:space-between;align-items:flex-start;margin:0 2px 14px;gap:10px">
|
|
1630
|
-
<div class="hint" style="flex:1">${S.runtimeCapabilities?esc(RUNTIME_UI_TEXT.providers):`管理模型 · 拖 ${IC.grip} 排序 · ${IC.settings} 配端点/key/思考深度 · 模型按群在「群·项目」选`}</div>
|
|
1658
|
+
<div class="hint" style="flex:1">${S.runtimeCapabilities?esc(runtimeAllows('POST','/api/providers/add')?RUNTIME_UI_TEXT.providers:RUNTIME_UI_TEXT.providersView):`管理模型 · 拖 ${IC.grip} 排序 · ${IC.settings} 配端点/key/思考深度 · 模型按群在「群·项目」选`}</div>
|
|
1631
1659
|
<div class="inline" style="gap:8px">${toggle}<button class="btn primary" id="addModel">${IC.plus} 添加模型</button></div></div>
|
|
1632
1660
|
<div class="grid cols2 stagger" id="pgrid">${cards}</div>
|
|
1633
1661
|
<h2 class="sec" style="margin-top:32px">插件库 · MCP</h2>
|
|
@@ -1665,7 +1693,7 @@ function renderProviders(){
|
|
|
1665
1693
|
$('#styleNew').onclick=()=>openStyleModal(null);
|
|
1666
1694
|
if($('#showAll'))$('#showAll').onclick=()=>{showAll=!showAll;renderProviders();};
|
|
1667
1695
|
$('#addModel').onclick=()=>openProviderModal(null);
|
|
1668
|
-
if(
|
|
1696
|
+
if(runtimeAllows('POST','/api/providers/order'))makeSortable($('#pgrid'),{item:'.pcard',key:c=>c.dataset.prov,save:o=>api('/api/providers/order','POST',{order:o})});
|
|
1669
1697
|
$$('#pgrid .pcard').forEach(card=>{
|
|
1670
1698
|
const name=card.dataset.prov, slot=$('.reply-slot',card);
|
|
1671
1699
|
card.addEventListener('click',async e=>{
|
|
@@ -1679,7 +1707,7 @@ function renderProviders(){
|
|
|
1679
1707
|
if(cx){const r=await api('/api/codex/verify','POST',{force:true});const lg=r.login||{};text=fmtUsage(r.usage);
|
|
1680
1708
|
/* 探测失败(fmtUsage 给空串)时保住原来的额度:这一行的 DOM 留着旧值,内存态却清空的话,
|
|
1681
1709
|
下次重画整行会凭空消失 —— 同一份状态两个消费者,漏一个就自相矛盾。 */
|
|
1682
|
-
codexStatus[name]={loggedIn:lg.loggedIn,mode:lg.mode,usageText:text||((codexStatus[name]||{}).usageText||'')};}
|
|
1710
|
+
codexStatus[name]={loggedIn:lg.loggedIn,mode:lg.mode,dead:lg.dead,verified:lg.verified,text:lg.text,usageText:text||(lg.dead?'':((codexStatus[name]||{}).usageText||''))};}
|
|
1683
1711
|
else{const r=await api('/api/usage/refresh','POST',{name});if(r.usage){usage[name]=r.usage;text=r.usage.value;}}
|
|
1684
1712
|
/* 就地改这一行,不走 renderProviders —— 整页重画会把这颗按钮连同它的 finally 一起换掉,
|
|
1685
1713
|
还会打断 hover 与试跑回执。拉不到时**留着旧值**:清空会被读成「这个模型没额度」。 */
|
|
@@ -1860,9 +1888,9 @@ function openProviderModal(name,opts){
|
|
|
1860
1888
|
})();
|
|
1861
1889
|
$('#amCancel').onclick=closeModal;
|
|
1862
1890
|
if(isCodex){$('#cxVerify').onclick=async()=>{const cb=$('#cxVerify');cb.disabled=true;const old=cb.innerHTML;cb.innerHTML='<span class="spin"></span>验证中';
|
|
1863
|
-
try{const r=await api('/api/codex/verify','POST',{});const lg=r.login||{},usageText=fmtUsage(r.usage);codexStatus[cxKey]={loggedIn:lg.loggedIn,mode:lg.mode,usageText};
|
|
1891
|
+
try{const r=await api('/api/codex/verify','POST',{});const lg=r.login||{},usageText=fmtUsage(r.usage);codexStatus[cxKey]={loggedIn:lg.loggedIn,mode:lg.mode,dead:lg.dead,verified:lg.verified,text:lg.text,usageText};
|
|
1864
1892
|
if($('#cxStatus'))$('#cxStatus').innerHTML=cxStatusHtml(cxKey);
|
|
1865
|
-
toast(lg.loggedIn?('Codex 订阅 ✓'+(usageText?' · '+usageText:(r.usage===null?'(额度取用失败:app-server?)':''))):'未登录 — 机器上跑 codex login',lg.loggedIn?'ok':'err');
|
|
1893
|
+
toast(lg.loggedIn?('Codex 订阅 ✓'+(usageText?' · '+usageText:(r.usage===null?'(额度取用失败:app-server?)':''))):(lg.dead?'登录态已失效 — 机器上重新跑 codex login':'未登录 — 机器上跑 codex login'),lg.loggedIn?'ok':'err');
|
|
1866
1894
|
renderProviders();
|
|
1867
1895
|
}catch(e){toast(e.message,'err');}finally{cb.disabled=false;cb.innerHTML=old;}};}
|
|
1868
1896
|
if(edit)$('#amDel').onclick=async()=>{if(!confirm('移除模型 '+name+' ?'))return;try{S=await api('/api/providers?name='+encodeURIComponent(name),'DELETE');toast('已移除','ok');closeModal();renderProviders();}catch(e){toast(e.message,'err');}};
|
|
@@ -1888,7 +1916,9 @@ function openProviderModal(name,opts){
|
|
|
1888
1916
|
if(edit)b.hidden=$('#amHide').checked;
|
|
1889
1917
|
try{const r=await api('/api/providers/add','POST',b);S=r;
|
|
1890
1918
|
const pc=r.providerChange||{};let msg=edit?'已保存':'已添加 '+nm;
|
|
1891
|
-
|
|
1919
|
+
// 持久处理流程:服务端照实报「这次写入重置了几个群的会话」,不发群内通知,也没有「启动后生效」这回事。
|
|
1920
|
+
if(S.runtimeCapabilities)msg+=runtimeChangeNote(pc);
|
|
1921
|
+
else if(pc.affected){
|
|
1892
1922
|
if(pc.modelChanged)msg+=pc.live?` · ${pc.affected} 个群随之切换,会话已重置并已在群内通知`:` · ${pc.affected} 个群使用此模型(daemon 未运行,启动后生效)`;
|
|
1893
1923
|
else if(pc.reChanged)msg+=pc.live?` · 已通知 ${pc.affected} 个使用中的群`:'';
|
|
1894
1924
|
}
|
|
@@ -2580,8 +2610,8 @@ function cronFullHtml(text){
|
|
|
2580
2610
|
}).join('');
|
|
2581
2611
|
}
|
|
2582
2612
|
const RUNTIME_UI_TEXT={
|
|
2583
|
-
"summary": "
|
|
2584
|
-
"groups": "
|
|
2613
|
+
"summary": "当前支持多群消息、流式卡片回复、忙时跟进、停止任务、空闲群开新会话、状态查看、认证管理、绑定新群、空闲群的模型(Codex / Claude Code)和项目目录切换、模型的添加与设置,以及后台运行时的软件升级。解除绑定、移除模型、控制台插话、定时任务、生图、画布与预览暂不可用。",
|
|
2614
|
+
"groups": "可绑定新群,修改模型、项目目录、权限、数据输出和忙时跟进;该群须没有进行中的任务和未结记录。切模型或项目会开始新的一段对话。停止任务和开新会话在「概览」,开新会话须等该群空闲。群里发 /model 也能切换模型。",
|
|
2585
2615
|
"skills": "当前支持飞书文字消息、忙时跟进、任务回复和停止。定时任务、生图、群消息管理、画布与预览暂未接入。",
|
|
2586
2616
|
"cron": "当前不执行定时任务;已有定义仅供查看。",
|
|
2587
2617
|
"board": "画布创建、同步与反馈暂未接入;已有链接仅供查看。",
|
|
@@ -2591,7 +2621,8 @@ const RUNTIME_UI_TEXT={
|
|
|
2591
2621
|
"resumeLabel": "恢复",
|
|
2592
2622
|
"steer": "群成员的后续文字消息可加入当前任务;只有确认消息已进入模型后才算送达。控制台插话和定时任务暂不可用。",
|
|
2593
2623
|
"preview": "当前版本未接入预览服务。",
|
|
2594
|
-
"providers": "
|
|
2624
|
+
"providers": "可添加模型,修改型号、端点、密钥、思考深度与推理强度,测试、隐藏或拖动排序;各群的模型在「群·项目」里切换。改型号、后端或端点时,用它的群须空闲,并会开始新的一段对话;默认模型和群正在用的模型不能隐藏。移除模型暂不可用。",
|
|
2625
|
+
"providersView": "当前可查看模型配置与登录状态;各群的模型在「群·项目」里切换。添加或修改模型暂不可用。",
|
|
2595
2626
|
"unavailable": "当前版本暂不支持此操作。"
|
|
2596
2627
|
};
|
|
2597
2628
|
function renderGroups(){
|
|
@@ -3370,7 +3401,8 @@ function openSay(chatId){
|
|
|
3370
3401
|
}catch(e){ toast(e.message,'err'); go.disabled=false; const t2=$('#sayGoTxt'); if(t2) t2.textContent=old }
|
|
3371
3402
|
};
|
|
3372
3403
|
}
|
|
3373
|
-
function closeModal(){clearTimeout(nsTimer);nsTimer=null;nsSeq++;sayRefresh=null;$('#modalBg').classList.remove('show');$('#modal').className='modal';
|
|
3404
|
+
function closeModal(){clearTimeout(nsTimer);nsTimer=null;nsSeq++;sayRefresh=null;$('#modalBg').classList.remove('show');$('#modal').className='modal';
|
|
3405
|
+
try{upgOnClose();}catch{}} // 升级弹窗:升级照常进行,只收起
|
|
3374
3406
|
$('#modalBg').onclick=e=>{if(e.target.id==='modalBg')closeModal();};
|
|
3375
3407
|
|
|
3376
3408
|
/* ── playground ── */
|
|
@@ -3494,7 +3526,7 @@ function renderSettings(){
|
|
|
3494
3526
|
:updMode==='foreground'?'前台运行不能在控制台升级:在运行 SintBot 的终端按 Ctrl-C 等待退出,重新运行一行安装命令 curl -fsSL https://sintbot.com/install.sh | bash,再用原来的启动命令启动。'
|
|
3495
3527
|
:updMode==='dev'?'开发版不在控制台升级,请用部署脚本。':'只安装检测到的稳定版本;所有群任务与队列清空后才会安装。需要可写的全局安装目录,不会提权;后台服务会自动重启,前台运行需手动重启。';
|
|
3496
3528
|
const updateCard=`<div class="card" style="grid-column:1/-1" id="updateCard"><div class="lbl">软件更新</div>
|
|
3497
|
-
<div id="updateStatus" class="hint" style="margin:0 0 12px;color:var(--ink)">${updateText}</div>
|
|
3529
|
+
<div id="updateStatus" class="hint" style="margin:0 0 12px;color:var(--ink);display:flex;align-items:center;gap:8px">${updMode==='managed'&&updateBusy?'<span class="spin"></span>':''}<span>${updateText}</span></div>
|
|
3498
3530
|
${upd&&upd.available?`<div class="hint" style="margin:-4px 0 12px">版本差异与发布说明:<a href="https://github.com/sintlab/sintbot/releases/tag/v${encodeURIComponent(upd.latestVersion)}" target="_blank" rel="noopener" style="color:var(--ember-soft)">查看 v${esc(upd.latestVersion)} 发布说明</a></div>`:''}
|
|
3499
3531
|
<div class="inline" style="gap:14px;flex-wrap:wrap;margin-bottom:12px">
|
|
3500
3532
|
<span class="dim">自动检查稳定版(启动时及每天)</span>
|
|
@@ -3503,6 +3535,8 @@ function renderSettings(){
|
|
|
3503
3535
|
<div class="inline" style="gap:8px;flex-wrap:wrap">
|
|
3504
3536
|
<button class="btn" id="updCheck" ${!upd?'disabled':''}>检查新版</button>
|
|
3505
3537
|
${updCanInstall?`<button class="btn primary" id="updInstall" ${!upd||!upd.available||updateBusy?'disabled':''}>现在升级</button>`:''}
|
|
3538
|
+
${updMode==='managed'&&updateJob&&(updateBusy||['failed','rolled-back'].includes(updateJob.state)||upgWatchId()===updateJob.id)
|
|
3539
|
+
?`<button class="btn" id="updProgress">${updateBusy?'查看进度':'查看上次升级'}</button>`:''}
|
|
3506
3540
|
<button class="btn" id="updIgnore" ${!upd||!upd.available?'disabled':''}>忽略此版本</button>
|
|
3507
3541
|
</div>
|
|
3508
3542
|
<div class="hint" style="margin-top:10px">${updHint}</div>
|
|
@@ -3557,8 +3591,11 @@ function renderSettings(){
|
|
|
3557
3591
|
$('#updCheck').onclick=async()=>{const b=$('#updCheck');b.disabled=true;b.textContent='检查中…';
|
|
3558
3592
|
try{await api('/api/update/check','POST',{});S=await api('/api/state');await refreshInbox();renderSettings();renderOverview();toast('版本检查完成','ok');}
|
|
3559
3593
|
catch(e){toast(e.message,'err');b.disabled=false;b.textContent='检查新版';}};
|
|
3594
|
+
if($('#updProgress'))$('#updProgress').onclick=()=>upgOpen(true);
|
|
3560
3595
|
if($('#updInstall'))$('#updInstall').onclick=async()=>{const u=S.update||{},version=u.latestVersion;if(!version)return;
|
|
3561
3596
|
const announce=$('#updAnnounce')?$('#updAnnounce').checked:false;
|
|
3597
|
+
// 后台托管:页内确认 → 进度弹窗(每一步、断线重连、完成后刷新到新版控制台)。前台 / 旧式更新器仍走下面的原生确认
|
|
3598
|
+
if(u.mode==='managed'){upgConfirm(announce);return;}
|
|
3562
3599
|
const what=u.mode==='managed'?'等所有群空闲、记录结清后装到单独的目录,再切换后台服务;切换期间约一分钟不处理新消息,新版本没连上全部群会自动换回。':`${announce?'安装完成后会向未暂停的群播报;':'本次不向群播报。'}所有群任务结束后才开始安装,后台服务会重启。`;
|
|
3563
3600
|
if(!confirm(`将从 v${u.currentVersion} 升级到 v${version}。${what}继续?`))return;
|
|
3564
3601
|
const b=$('#updInstall');b.disabled=true;
|
|
@@ -3927,9 +3964,209 @@ document.addEventListener('click',(e)=>{const b=e.target.closest&&e.target.close
|
|
|
3927
3964
|
function renderAll(){renderOverview();renderProviders();renderGroups();renderPlayground();renderSettings();updateDaemon();updateTopFxApp();applyRuntimeCapabilities();}
|
|
3928
3965
|
function updateTopUpdateBadge(){
|
|
3929
3966
|
const badge=$('#topUpdateBadge');if(!badge)return;
|
|
3930
|
-
|
|
3967
|
+
const busy=upgManaged()&&upgIsActive(upgJob());
|
|
3968
|
+
badge.hidden=!(S&&S.update&&(S.update.available||busy));
|
|
3969
|
+
badge.classList.toggle('upgrading',busy);
|
|
3970
|
+
const tip=busy?'正在升级,查看进度':'发现新版本,打开软件更新';
|
|
3971
|
+
if(badge.title!==tip){badge.title=tip;badge.setAttribute('aria-label',tip);}
|
|
3972
|
+
upgEnsureWatch();
|
|
3931
3973
|
}
|
|
3974
|
+
|
|
3975
|
+
/* UPGRADE_PROGRESS_BEGIN ── 控制台升级的进度弹窗 ──────────────────────────────────────────
|
|
3976
|
+
点「现在升级」→ 页内确认 → 进度弹窗。升级期间每 1.5 秒读一次 GET /api/update;服务切换那几秒
|
|
3977
|
+
连不上(或反代回 502)就显示「服务正在重启,页面会自动重连」,不当成错误;重启后旧令牌作废(环回
|
|
3978
|
+
免密模式)则交给 checkStaleAuth 整页重载。新版本连上全部群、任务记为 done 后,这一页还是旧版前端
|
|
3979
|
+
⇒ 倒数 3 秒整页刷新,刷新后按 sessionStorage 记下的任务 id 再把结果弹出来一次。
|
|
3980
|
+
每一步的状态只从任务记录(state / prefix / installed / steps)推出来,不靠页面记忆。 */
|
|
3981
|
+
const UPG_PHASES=[
|
|
3982
|
+
['wait','等待所有群空闲'],
|
|
3983
|
+
['install','下载并安装新版本'],
|
|
3984
|
+
['stop','停止旧服务'],
|
|
3985
|
+
['backup','核对群记录并备份数据'],
|
|
3986
|
+
['preflight','新版本预检'],
|
|
3987
|
+
['start','切换并启动新版本'],
|
|
3988
|
+
['connect','等待全部群连上'],
|
|
3989
|
+
];
|
|
3990
|
+
function upgIsActive(j){return !!(j&&['queued','installing','switching'].includes(j.state));}
|
|
3991
|
+
function upgVer(v){return String(v||'').trim().replace(/^v/i,'');}
|
|
3992
|
+
function upgStep(j,name){const s=((j&&j.steps)||[]).find(x=>x&&x.step===name);return s?(s.ok?'ok':'bad'):'';}
|
|
3993
|
+
// → [{key,label,state:'done'|'active'|'fail'|'todo'}]:最后一个已到达的步骤之前全算完成,下一步是
|
|
3994
|
+
// 进行中(任务还在跑)或失败(任务已失败 / 已换回);换回旧版本时多一行「换回 vX」。
|
|
3995
|
+
function upgPhases(j){
|
|
3996
|
+
if(!j)return [];
|
|
3997
|
+
const st=j.state, ok=n=>upgStep(j,n)==='ok';
|
|
3998
|
+
const reached={
|
|
3999
|
+
wait:!!(j.prefix||j.installed)||st==='installing'||st==='switching',
|
|
4000
|
+
install:!!j.installed,
|
|
4001
|
+
stop:ok('stop service'),
|
|
4002
|
+
backup:ok('journals unchanged')&&ok('data backed up'),
|
|
4003
|
+
preflight:ok('new version preflight'),
|
|
4004
|
+
start:ok('service entry replaced')&&ok('start new version'),
|
|
4005
|
+
connect:st==='done',
|
|
4006
|
+
};
|
|
4007
|
+
let last=-1;UPG_PHASES.forEach(([k],i)=>{if(reached[k])last=i;});
|
|
4008
|
+
if(st==='done')last=UPG_PHASES.length-1;
|
|
4009
|
+
const active=upgIsActive(j), failed=st==='failed'||st==='rolled-back';
|
|
4010
|
+
const out=UPG_PHASES.map(([key,label],i)=>({key,label,
|
|
4011
|
+
state:i<=last?'done':i===last+1?(active?'active':failed?'fail':'todo'):'todo'}));
|
|
4012
|
+
if(upgStep(j,'rollback')){
|
|
4013
|
+
const back=upgStep(j,'previous version started');
|
|
4014
|
+
out.push({key:'rollback',label:`换回 v${upgVer(j.from)}`,state:back==='ok'||st==='rolled-back'?'done':back==='bad'||st==='failed'?'fail':'active'});
|
|
4015
|
+
}
|
|
4016
|
+
return out;
|
|
4017
|
+
}
|
|
4018
|
+
// 连不上时是不是在切换:已进入安装 / 已装好 / 已在切换(排队等空闲时服务不会自己重启)
|
|
4019
|
+
function upgRestarting(j){return !!(j&&(j.state==='switching'||j.state==='installing'||j.installed));}
|
|
4020
|
+
function upgDur(ms){const s=Math.max(0,Math.round(ms/1000));return s<60?`${s} 秒`:`${Math.floor(s/60)} 分 ${String(s%60).padStart(2,'0')} 秒`;}
|
|
4021
|
+
/* UPGRADE_PROGRESS_LOGIC_END */
|
|
4022
|
+
const upg={timer:0,tick:0,polling:false,open:false,view:'',offlineSince:0,waiting:[],reloadAt:0,toasted:''};
|
|
4023
|
+
const UPG_PAGE_VERSION=upgVer((document.querySelector('.mark .ver')||{}).textContent);
|
|
4024
|
+
function upgWatchId(){try{return sessionStorage.getItem('sbot-upgrade-watch')||'';}catch{return '';}}
|
|
4025
|
+
function upgSetWatch(id){try{if(id)sessionStorage.setItem('sbot-upgrade-watch',id);else sessionStorage.removeItem('sbot-upgrade-watch');}catch{}}
|
|
4026
|
+
// 「在后台继续」收起的任务 id:服务重启后页面会整页重载(环回免密模式换令牌),收起的弹窗不能因此又弹出来
|
|
4027
|
+
function upgHiddenId(){try{return sessionStorage.getItem('sbot-upgrade-hidden')||'';}catch{return '';}}
|
|
4028
|
+
function upgSetHidden(id){try{if(id)sessionStorage.setItem('sbot-upgrade-hidden',id);else sessionStorage.removeItem('sbot-upgrade-hidden');}catch{}}
|
|
4029
|
+
function upgJob(){return (S&&S.update&&S.update.job)||null;}
|
|
4030
|
+
function upgManaged(){return !!(S&&S.update&&S.update.mode==='managed');}
|
|
4031
|
+
function upgModalShown(){return !!(upg.open&&upg.view==='progress'&&$('#modalBg').classList.contains('show')&&$('#modal').classList.contains('upg'));}
|
|
4032
|
+
function upgSchedule(ms=1500){clearTimeout(upg.timer);upg.timer=setTimeout(upgPoll,ms);}
|
|
4033
|
+
// 由 updateTopUpdateBadge 在每次状态更新时调用:任务在跑就保证轮询在走;这一页发起(或看过)的任务
|
|
4034
|
+
// 刚结束、页面刚刷新过来 ⇒ 把结果弹出来一次。
|
|
4035
|
+
function upgEnsureWatch(){
|
|
4036
|
+
if(!upgManaged())return;
|
|
4037
|
+
const j=upgJob();if(!j)return;
|
|
4038
|
+
if(upgIsActive(j)&&!upg.timer&&!upg.polling)upgSchedule(1500);
|
|
4039
|
+
if(upg.open||upgWatchId()!==j.id||$('#modalBg').classList.contains('show'))return;
|
|
4040
|
+
if(upgHiddenId()!==j.id)upgOpen(false);
|
|
4041
|
+
else if(!upgIsActive(j))upgNotifyDone(j); // 收起过:结束时只发一条提示
|
|
4042
|
+
}
|
|
4043
|
+
function upgNotifyDone(j){
|
|
4044
|
+
if(!j||upg.toasted===j.id)return;
|
|
4045
|
+
upg.toasted=j.id;upgSetWatch('');upgSetHidden('');
|
|
4046
|
+
toast(j.message||(j.state==='done'?`已升级到 v${upgVer(j.to)}`:'升级没有完成'),j.state==='done'?'ok':'err');
|
|
4047
|
+
}
|
|
4048
|
+
async function upgPoll(){
|
|
4049
|
+
upg.timer=0;if(upg.polling)return;upg.polling=true;
|
|
4050
|
+
let r=null,data=null;
|
|
4051
|
+
try{
|
|
4052
|
+
try{r=await fetch('/api/update',{headers:{'X-Sbot-Token':TOKEN},cache:'no-store'});}catch{}
|
|
4053
|
+
if(r&&r.status===401){upgRender();checkStaleAuth();return;} // 重启后令牌作废:整页重载或落到登录框
|
|
4054
|
+
if(r&&r.ok){try{data=await r.json();}catch{}}
|
|
4055
|
+
if(!data||!data.currentVersion){ // 断线 / 反代 502 / 服务重启中
|
|
4056
|
+
if(!upg.offlineSince)upg.offlineSince=Date.now();
|
|
4057
|
+
upgRender();upgSchedule(2000);return;
|
|
4058
|
+
}
|
|
4059
|
+
const wasOffline=!!upg.offlineSince;upg.offlineSince=0;
|
|
4060
|
+
upg.waiting=Array.isArray(data.waitingGroups)?data.waitingGroups:[];
|
|
4061
|
+
const {waitingGroups,...update}=data;
|
|
4062
|
+
S.update={...(S.update||{}),...update};
|
|
4063
|
+
const j=update.job;
|
|
4064
|
+
if(upgIsActive(j)){upgRender();updateTopUpdateBadge();upgSchedule(1500);return;}
|
|
4065
|
+
upgFinished(j,update,wasOffline);
|
|
4066
|
+
}finally{upg.polling=false;}
|
|
4067
|
+
}
|
|
4068
|
+
// 任务结束(done / failed / rolled-back):这一页是旧版前端就倒数刷新;弹窗被收起了就只发一条提示。
|
|
4069
|
+
async function upgFinished(j,update,wasOffline){
|
|
4070
|
+
const mine=!!(j&&upgWatchId()===j.id);
|
|
4071
|
+
const stale=!!(j&&j.state==='done'&&UPG_PAGE_VERSION&&upgVer(update.currentVersion)!==UPG_PAGE_VERSION);
|
|
4072
|
+
if(j&&mine&&stale&&upgModalShown()&&!upg.reloadAt){
|
|
4073
|
+
upg.reloadAt=Date.now()+3000;upgRender();updateTopUpdateBadge();setTimeout(()=>location.reload(),3000);return;
|
|
4074
|
+
}
|
|
4075
|
+
if(j&&mine&&!upgModalShown())upgNotifyDone(j);
|
|
4076
|
+
upgRender();
|
|
4077
|
+
try{S=await api('/api/state');}catch{}
|
|
4078
|
+
updateTopUpdateBadge();
|
|
4079
|
+
if(!busyInteracting()){renderSettings();renderOverview();}
|
|
4080
|
+
else if(wasOffline&&!upgModalShown())renderOverview();
|
|
4081
|
+
}
|
|
4082
|
+
function upgConfirm(announce){
|
|
4083
|
+
const u=S.update||{};
|
|
4084
|
+
$('#modal').className='modal upg';
|
|
4085
|
+
$('#modal').innerHTML=`<h3>升级到 v${esc(upgVer(u.latestVersion))}</h3>
|
|
4086
|
+
<div class="upg-sub"><span class="mono">v${esc(upgVer(u.currentVersion))} → v${esc(upgVer(u.latestVersion))}</span></div>
|
|
4087
|
+
<div class="upg-note">等所有群空闲、记录结清后装到单独的目录,再切换后台服务;切换期间约一分钟不处理新消息,新版本没连上全部群会自动换回。</div>
|
|
4088
|
+
<div class="hint" style="margin:-6px 0 18px">开始后这里会显示每一步的进度;服务重启时页面会自动重连。</div>
|
|
4089
|
+
<div class="upg-btns"><button class="btn" id="upgCancel">取消</button><button class="btn primary" id="upgGo">开始升级</button></div>`;
|
|
4090
|
+
upg.open=true;upg.view='confirm';
|
|
4091
|
+
$('#modalBg').classList.add('show');
|
|
4092
|
+
$('#upgCancel').onclick=()=>closeModal();
|
|
4093
|
+
$('#upgGo').onclick=async()=>{
|
|
4094
|
+
const b=$('#upgGo');b.disabled=true;b.innerHTML='<span class="spin"></span> 正在提交…';
|
|
4095
|
+
try{
|
|
4096
|
+
const r=await api('/api/update/start','POST',{version:u.latestVersion,announce:!!announce});
|
|
4097
|
+
S.update={...(S.update||{}),...r};
|
|
4098
|
+
upgSetWatch(r.job&&r.job.id);upg.reloadAt=0;
|
|
4099
|
+
upgOpen(true);updateTopUpdateBadge();upgSchedule(600);
|
|
4100
|
+
}catch(e){
|
|
4101
|
+
if(e.payload&&e.payload.update)S.update={...(S.update||{}),...e.payload.update};
|
|
4102
|
+
toast(e.message,'err');b.disabled=false;b.textContent='开始升级';
|
|
4103
|
+
}
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
// 打开进度 / 结果弹窗。user=true:用户点的(「查看进度」、顶栏图标、刚确认),记下任务 id 以便刷新后接着看。
|
|
4107
|
+
function upgOpen(user){
|
|
4108
|
+
const j=upgJob();if(!j)return;
|
|
4109
|
+
if(user){upgSetWatch(j.id);upgSetHidden('');}
|
|
4110
|
+
$('#modal').className='modal upg';
|
|
4111
|
+
upg.open=true;upg.view='progress';
|
|
4112
|
+
$('#modalBg').classList.add('show');
|
|
4113
|
+
upgRender();
|
|
4114
|
+
clearInterval(upg.tick);upg.tick=setInterval(()=>{if(!upgModalShown()){clearInterval(upg.tick);upg.tick=0;return;}upgRender();},1000);
|
|
4115
|
+
if(upgIsActive(j)&&!upg.timer&&!upg.polling)upgSchedule(300);
|
|
4116
|
+
}
|
|
4117
|
+
// 弹窗被关掉(按钮、点遮罩、别的弹窗):升级照常进行,顶栏图标保持转圈,结束时只发提示
|
|
4118
|
+
function upgOnClose(){
|
|
4119
|
+
if(!upg.open)return;
|
|
4120
|
+
const wasProgress=upg.view==='progress';
|
|
4121
|
+
upg.open=false;upg.view='';clearInterval(upg.tick);upg.tick=0;
|
|
4122
|
+
const j=upgJob();
|
|
4123
|
+
if(wasProgress&&upgIsActive(j))upgSetHidden(j.id);
|
|
4124
|
+
else if(wasProgress&&j&&upgWatchId()===j.id){upgSetWatch('');upgSetHidden('');}
|
|
4125
|
+
// 结果看完了:设置页那张卡在弹窗开着时没重画,这里补一次
|
|
4126
|
+
if(wasProgress&&!upgIsActive(j))setTimeout(()=>{if(!busyInteracting()){renderSettings();renderOverview();}},0);
|
|
4127
|
+
}
|
|
4128
|
+
function upgRender(){
|
|
4129
|
+
const j=upgJob(), statusBox=$('#updateStatus');
|
|
4130
|
+
if(statusBox&&j&&upgManaged()&&upgIsActive(j)){
|
|
4131
|
+
const text=upg.offlineSince?(upgRestarting(j)?'服务正在重启,页面会自动重连…':'暂时连不上服务,正在重试…')
|
|
4132
|
+
:j.state==='switching'&&upgStep(j,'start new version')==='ok'?'新版本已启动,正在等全部群连上…':(j.message||j.state);
|
|
4133
|
+
statusBox.innerHTML=`<span class="spin"></span><span>${esc(text)}</span>`;
|
|
4134
|
+
}
|
|
4135
|
+
if(!upgModalShown()||!j)return;
|
|
4136
|
+
// 安装一结束,切换进程不到一秒就停掉旧服务:页面最后看到的多半还是「安装中」。这时连不上 = 正在切换
|
|
4137
|
+
const view=upg.offlineSince&&upgRestarting(j)&&j.state!=='switching'?{...j,state:'switching',installed:true}:j;
|
|
4138
|
+
const active=upgIsActive(view), st=view.state, phases=upgPhases(view);
|
|
4139
|
+
const end=active?Date.now():(+j.finishedAt||+j.updatedAt||Date.now());
|
|
4140
|
+
const took=upgDur(end-(+j.createdAt||end));
|
|
4141
|
+
const doneN=phases.filter(p=>p.key!=='rollback'&&p.state==='done').length;
|
|
4142
|
+
const pct=st==='done'?100:Math.max(4,Math.round(doneN/UPG_PHASES.length*100));
|
|
4143
|
+
const to=esc(upgVer(j.to)), from=esc(upgVer(j.from));
|
|
4144
|
+
const head=st==='done'?`<span class="upg-mark ok">✓</span><span>已升级到 v${to}</span>`
|
|
4145
|
+
:active?`<span class="spin"></span><span>正在升级到 v${to}</span>`
|
|
4146
|
+
:`<span class="upg-mark fail">!</span><span>升级没有完成</span>`;
|
|
4147
|
+
let note,noteCls='';
|
|
4148
|
+
if(active&&upg.offlineSince){note=st==='switching'?'服务正在重启,页面会自动重连…':'暂时连不上服务,正在重试…';noteCls='warn';}
|
|
4149
|
+
else if(st==='switching'&&upgStep(view,'start new version')==='ok'){note='新版本已启动,正在等全部群连上…';}
|
|
4150
|
+
else if(st==='queued'&&upg.waiting.length){note=`还在运行的群:${upg.waiting.map(g=>g.name).join('、')}`;}
|
|
4151
|
+
else if(st==='done'&&upg.reloadAt){note=`页面将在 ${Math.max(0,Math.ceil((upg.reloadAt-Date.now())/1000))} 秒后刷新,载入新版控制台`;}
|
|
4152
|
+
else{note=j.message||st;if(!active&&st!=='done')noteCls='fail';}
|
|
4153
|
+
const icon=p=>p.state==='done'?'✓':p.state==='fail'?'!':p.state==='active'?'<span class="spin"></span>':'';
|
|
4154
|
+
const btns=active?`<button class="btn" id="upgHide">在后台继续</button>`
|
|
4155
|
+
:st==='done'&&upg.reloadAt?`<button class="btn primary" id="upgReload">现在刷新</button>`
|
|
4156
|
+
:`<button class="btn${st==='done'?' primary':''}" id="upgDone">${st==='done'?'完成':'关闭'}</button>`;
|
|
4157
|
+
$('#modal').innerHTML=`<h3>${head}</h3>
|
|
4158
|
+
<div class="upg-sub"><span class="mono">v${from} → v${to}</span><span>${active?`已用 ${took}`:`用时 ${took}`}</span></div>
|
|
4159
|
+
<div class="upg-bar ${active?'run':st==='done'?'ok':'fail'}"><i style="width:${pct}%"></i></div>
|
|
4160
|
+
<ol class="upg-steps">${phases.map(p=>`<li class="${p.state}"><span class="dot">${icon(p)}</span><span>${esc(p.label)}</span></li>`).join('')}</ol>
|
|
4161
|
+
<div class="upg-note ${noteCls}">${esc(note)}</div>
|
|
4162
|
+
<div class="upg-btns">${btns}</div>`;
|
|
4163
|
+
const hide=$('#upgHide');if(hide)hide.onclick=()=>closeModal();
|
|
4164
|
+
const done=$('#upgDone');if(done)done.onclick=()=>closeModal();
|
|
4165
|
+
const rl=$('#upgReload');if(rl)rl.onclick=()=>location.reload();
|
|
4166
|
+
}
|
|
4167
|
+
/* UPGRADE_PROGRESS_END */
|
|
3932
4168
|
(()=>{const badge=$('#topUpdateBadge');if(!badge)return;badge.onclick=()=>{
|
|
4169
|
+
if(upgManaged()&&upgIsActive(upgJob())){upgOpen(true);return;}
|
|
3933
4170
|
const nav=$('nav [data-tab="settings"]');if(nav)nav.click();
|
|
3934
4171
|
const card=$('#updateCard');if(!card)return;
|
|
3935
4172
|
requestAnimationFrame(()=>{card.scrollIntoView({behavior:'smooth',block:'start'});
|
|
@@ -4039,8 +4276,8 @@ updateThemeBtn();
|
|
|
4039
4276
|
const I18N_EN=[
|
|
4040
4277
|
["需要处理", "Needs attention"],
|
|
4041
4278
|
["当前可用操作", "Available controls"],
|
|
4042
|
-
["
|
|
4043
|
-
["
|
|
4279
|
+
["当前支持多群消息、流式卡片回复、忙时跟进、停止任务、空闲群开新会话、状态查看、认证管理、绑定新群、空闲群的模型(Codex / Claude Code)和项目目录切换、模型的添加与设置,以及后台运行时的软件升级。解除绑定、移除模型、控制台插话、定时任务、生图、画布与预览暂不可用。", "Available: group messages, streaming reply cards, busy-turn followups, stopping tasks, new sessions for idle groups, live status, authentication, binding new groups, switching the model (Codex / Claude Code) or project of an idle group, adding and configuring models, and software upgrades in a background run. Unbinding, removing models, console messages, schedules, image generation, canvas and previews are unavailable."],
|
|
4280
|
+
["可绑定新群,修改模型、项目目录、权限、数据输出和忙时跟进;该群须没有进行中的任务和未结记录。切模型或项目会开始新的一段对话。停止任务和开新会话在「概览」,开新会话须等该群空闲。群里发 /model 也能切换模型。", "Bind new groups and change the model, project folder, permissions, data display and busy-turn followups of a group with no running task or unsettled record. Changing the model or project starts a new conversation. Stop tasks and start new sessions in Overview; a new session needs the group to be idle. Members can also switch models with /model in the group."],
|
|
4044
4281
|
["当前支持飞书文字消息、忙时跟进、任务回复和停止。定时任务、生图、群消息管理、画布与预览暂未接入。", "Feishu text messages, busy-turn followups, task replies and stopping are supported. Schedules, image generation, group message management, canvas and previews are unavailable."],
|
|
4045
4282
|
["当前不执行定时任务;已有定义仅供查看。", "Schedules are not executed. Existing definitions are shown for reference only."],
|
|
4046
4283
|
["画布创建、同步与反馈暂未接入;已有链接仅供查看。", "Canvas creation, sync and feedback are unavailable. Existing links are view-only."],
|
|
@@ -4051,6 +4288,10 @@ const I18N_EN=[
|
|
|
4051
4288
|
["群成员的后续文字消息可加入当前任务;只有确认消息已进入模型后才算送达。控制台插话和定时任务暂不可用。", "Followup text messages from group members can join the current task. Delivery is confirmed only when the model receives the message. Console messages and schedules are unavailable."],
|
|
4052
4289
|
["当前版本未接入预览服务。", "Preview services are unavailable in this version."],
|
|
4053
4290
|
["当前可查看模型配置与登录状态;各群的模型在「群·项目」里切换。添加或修改模型暂不可用。", "Model configuration and login status are shown here. Switch each group's model under Groups. Adding or changing models is unavailable."],
|
|
4291
|
+
["可添加模型,修改型号、端点、密钥、思考深度与推理强度,测试、隐藏或拖动排序;各群的模型在「群·项目」里切换。改型号、后端或端点时,用它的群须空闲,并会开始新的一段对话;默认模型和群正在用的模型不能隐藏。移除模型暂不可用。", "Add models; change their model id, endpoint, key, thinking depth and reasoning effort; test, hide or drag to reorder them. Switch each group's model under Groups. Changing a model id, backend or endpoint needs the groups using it to be idle and starts a new conversation for them. The default model and models in use by a group cannot be hidden. Removing models is unavailable."],
|
|
4292
|
+
["当前只能添加或修改 Codex 或 Claude Code 模型", "Only Codex or Claude Code models can be added or changed right now."],
|
|
4293
|
+
["默认模型须是列表中未隐藏的 Codex 或 Claude Code 模型", "The default model must be a visible Codex or Claude Code model from the list."],
|
|
4294
|
+
["有群因未解决的故障暂停处理,它用的模型、后端或端点要等处理完并重启后才能改", "A group is held after an unresolved fault; the model, backend or endpoint it uses can change only after the fault is handled and the service restarts."],
|
|
4054
4295
|
["当前版本暂不支持此操作。", "This operation is unavailable in the current version."],
|
|
4055
4296
|
|
|
4056
4297
|
["控制台","Console"],
|
|
@@ -4114,6 +4355,10 @@ const I18N_EN=[
|
|
|
4114
4355
|
["向这个群插一句 —— 像真人消息那样驱动一轮","Interject in this group — drives a turn like a real message"],
|
|
4115
4356
|
["停止当前任务(保留会话)","Stop the current task (keeps the session)"],
|
|
4116
4357
|
["开新会话 — 运行中会先中断当前任务并清空队列(同群内 /new)","New session — if running, interrupts the current task and clears the queue first (same as /new in the group)"],
|
|
4358
|
+
["开新会话 — 仅在该群空闲、记录全部结账后可用;运行中请先停止,结束后再开(同群内 /new)","New session — only when this group is idle and every record is settled; if it is running, stop it first and start the new session once it ends (same as /new in the group)"],
|
|
4359
|
+
["该群有任务进行中或记录未结账,先停止或等它结束再开新会话","This group has a task running or an unsettled record. Stop it or wait for it to finish, then start a new session."],
|
|
4360
|
+
["此群未运行,未开新会话","This group is not running; no new session was started."],
|
|
4361
|
+
["此群当前未开放,未开新会话","This group is not open right now; no new session was started."],
|
|
4117
4362
|
["还没有群项目 · 去「群·项目」配置","No group projects yet · set one up on the Groups · Projects page"],
|
|
4118
4363
|
["健康","Health"],
|
|
4119
4364
|
["并发","Concurrency"],
|
|
@@ -4166,6 +4411,10 @@ const I18N_EN=[
|
|
|
4166
4411
|
["重新拉取额度","Refresh quota"],
|
|
4167
4412
|
["点卡片右上角「配置」里的「检测登录」确认这台机器的 Claude 登录态","Use \"Check sign-in\" under \"Configure\" (top right of the card) to confirm this machine's Claude sign-in state"],
|
|
4168
4413
|
["订阅登录·未验证","Subscription · unverified"],
|
|
4414
|
+
["已验证","Verified"],
|
|
4415
|
+
["登录态已失效","Sign-in expired"],
|
|
4416
|
+
["登录态已失效 — 机器上重新跑 codex login","Sign-in expired — run codex login again on this machine"],
|
|
4417
|
+
["未验证(本机有登录文件,服务端暂时没探到)","Unverified (this machine has a sign-in file; the server couldn't be reached just now)"],
|
|
4169
4418
|
["这台机器还没登录 Claude —— 跑 claude auth login","This machine is not signed in to Claude — run claude auth login"],
|
|
4170
4419
|
["订阅登录","Subscription sign-in"],
|
|
4171
4420
|
["点下方「验证登录」确认 codex 订阅登录态 + 拉取额度","Use \"Verify sign-in\" below to confirm the codex subscription sign-in and fetch the quota"],
|
|
@@ -4777,6 +5026,27 @@ const I18N_EN=[
|
|
|
4777
5026
|
["只安装检测到的稳定版本:等所有群空闲、记录结清后装到单独的目录,再切换后台服务;新版本没在 90 秒内连上全部群会自动换回。旧版本保留用于回滚,不提权,不在群里播报。","Installs only the detected stable version: once every group is idle and settled, it goes into its own folder and the background service switches to it; if the new version does not connect every group within 90 seconds, the previous one is restored. The previous version is kept for rollback. No elevated privileges and no group announcements."],
|
|
4778
5027
|
["前台运行不能在控制台升级:在运行 SintBot 的终端按 Ctrl-C 等待退出,重新运行一行安装命令 curl -fsSL https://sintbot.com/install.sh | bash,再用原来的启动命令启动。","A foreground run cannot upgrade from the console: press Ctrl-C in the terminal running SintBot and wait for it to exit, rerun the one-line installer curl -fsSL https://sintbot.com/install.sh | bash, then start with your usual command."],
|
|
4779
5028
|
["开发版不在控制台升级,请用部署脚本。","Development builds are not upgraded from the console; use the deployment scripts."],
|
|
5029
|
+
["查看进度","View progress"],
|
|
5030
|
+
["查看上次升级","View last upgrade"],
|
|
5031
|
+
["正在升级,查看进度","Upgrading; view progress"],
|
|
5032
|
+
["等所有群空闲、记录结清后装到单独的目录,再切换后台服务;切换期间约一分钟不处理新消息,新版本没连上全部群会自动换回。","Once every group is idle and settled, it installs into its own folder and switches the background service; new messages wait about a minute during the switch, and the previous version is restored if the new one does not connect every group."],
|
|
5033
|
+
["开始后这里会显示每一步的进度;服务重启时页面会自动重连。","Once started, this window shows each step; the page reconnects by itself while the service restarts."],
|
|
5034
|
+
["开始升级","Start upgrade"],
|
|
5035
|
+
["正在提交…","Submitting…"],
|
|
5036
|
+
["等待所有群空闲","Waiting for every group to be idle"],
|
|
5037
|
+
["下载并安装新版本","Download and install the new version"],
|
|
5038
|
+
["停止旧服务","Stop the current service"],
|
|
5039
|
+
["核对群记录并备份数据","Check group records and back up data"],
|
|
5040
|
+
["新版本预检","Preflight the new version"],
|
|
5041
|
+
["切换并启动新版本","Switch to and start the new version"],
|
|
5042
|
+
["等待全部群连上","Wait for every group to connect"],
|
|
5043
|
+
["升级没有完成","The upgrade did not complete"],
|
|
5044
|
+
["服务正在重启,页面会自动重连…","The service is restarting; this page reconnects by itself…"],
|
|
5045
|
+
["暂时连不上服务,正在重试…","Cannot reach the service right now; retrying…"],
|
|
5046
|
+
["新版本已启动,正在等全部群连上…","The new version has started; waiting for every group to connect…"],
|
|
5047
|
+
["在后台继续","Continue in background"],
|
|
5048
|
+
["现在刷新","Reload now"],
|
|
5049
|
+
["关闭","Close"],
|
|
4780
5050
|
["访问地址正在切换;页面即将重载","Changing the access address; this page will reload"],
|
|
4781
5051
|
["访问设置已保存","Access settings saved"],
|
|
4782
5052
|
["开启局域网访问前要设置至少 6 位访问密码","Set an access password of at least 6 characters before enabling LAN access"],
|
|
@@ -5253,6 +5523,12 @@ const I18N_EN_RX=[
|
|
|
5253
5523
|
[/^闲置 ([\d.]+) 分钟自动停$/,"Auto-stops after $1 min idle"],
|
|
5254
5524
|
[/^最长 ([\d.]+) 小时自动停$/,"Auto-stops after $1 h max"],
|
|
5255
5525
|
[/^已登录 · (.+)$/,"Signed in · $1"],
|
|
5526
|
+
// codex 登录态三态(src/agent/codexstatus.js describeLogin 的全部句式;尾巴「 · 套餐 · 邮箱」「 — 原因」原样带过去)。
|
|
5527
|
+
// ⚠ 必须排在下面那条通用「未验证 — …」之前,否则带具体原因的句子只会被译掉开头两个字
|
|
5528
|
+
[/^订阅登录已验证( · [\s\S]+)?$/,"Subscription sign-in verified$1"],
|
|
5529
|
+
[/^登录态已失效\(服务端拒绝了这台机器的令牌\)— 在这台机器上重新跑 codex login( · [\s\S]+)?$/,"Sign-in expired (the server rejected this machine's token) — run codex login again on this machine$1"],
|
|
5530
|
+
[/^未验证 — 本机没有登录文件\(可能未登录\)( — [\s\S]*)?$/,"Unverified — no sign-in file on this machine (possibly not signed in)$1"],
|
|
5531
|
+
[/^未验证且令牌已过期\((\d{4}-\d{2}-\d{2})\),很可能要重新登录( — [\s\S]*)?$/,"Unverified and the token expired ($1) — you most likely need to sign in again$2"],
|
|
5256
5532
|
[/^未验证 — ([\s\S]*)$/,"Unverified — $1"],
|
|
5257
5533
|
[/^codex login status: (.+) · 已验证$/,"codex login status: $1 · verified"],
|
|
5258
5534
|
[/^窗口:(.*)$/,"Window:$1"],
|
|
@@ -5275,6 +5551,16 @@ const I18N_EN_RX=[
|
|
|
5275
5551
|
[/^已添加 (\S+) · (\d+) 个群随之切换,会话已重置并已在群内通知$/,"Added $1 · $2 group(s) switched with it; sessions reset and the groups notified"],
|
|
5276
5552
|
[/^已添加 (\S+) · (\d+) 个群使用此模型\(daemon 未运行,启动后生效\)$/,"Added $1 · $2 group(s) use this model (daemon not running; takes effect on start)"],
|
|
5277
5553
|
[/^已添加 (\S+) · 已通知 (\d+) 个使用中的群$/,"Added $1 · $2 active group(s) notified"],
|
|
5554
|
+
[/^已保存 · (\d+) 个群已开始新的一段对话$/,"Saved · $1 group(s) started a new conversation"],
|
|
5555
|
+
[/^已添加 (\S+) · (\d+) 个群已开始新的一段对话$/,"Added $1 · $2 group(s) started a new conversation"],
|
|
5556
|
+
[/^模型「(.+)」的后端不受支持;只能添加或修改 Codex 或 Claude Code 模型$/,"The backend of model “$1” is not supported; only Codex or Claude Code models can be added or changed."],
|
|
5557
|
+
[/^「(.+)」是默认模型,先把默认模型换成别的再隐藏$/,"“$1” is the default model. Choose another default model before hiding it."],
|
|
5558
|
+
[/^「(.+)」是默认模型,先把默认模型换成别的再移除$/,"“$1” is the default model. Choose another default model before removing it."],
|
|
5559
|
+
[/^「(.+)」正被 (\d+) 个群使用,先把这些群切到其它模型再隐藏$/,"“$1” is used by $2 group(s). Switch them to another model before hiding it."],
|
|
5560
|
+
[/^「(.+)」正被 (\d+) 个群使用,先把这些群切到其它模型再移除$/,"“$1” is used by $2 group(s). Switch them to another model before removing it."],
|
|
5561
|
+
[/^「(.+)」有任务进行中或记录未结账,等它结束后再改它用的模型、后端或端点$/,"“$1” has a task running or an unsettled record. Wait for it to finish before changing the model, backend or endpoint it uses."],
|
|
5562
|
+
[/^默认模型已改为 (\S+)$/,"Default model changed to $1"],
|
|
5563
|
+
[/^(\d+) 个群已开始新的一段对话$/,"$1 group(s) started a new conversation"],
|
|
5278
5564
|
[/^已添加 (\S+)$/,"Added $1"],
|
|
5279
5565
|
[/^叫法:(.+)$/,"Naming: $1"],
|
|
5280
5566
|
[/^(\d+) 个图标 · 语义名 (\d+) 条 · 兜底 (.+)$/,"$1 icons · $2 semantic names · fallback $3"],
|
|
@@ -5404,6 +5690,15 @@ const I18N_EN_RX=[
|
|
|
5404
5690
|
[/^v([\d.]+) 已安装,等所有群空闲后切换$/,"v$1 is installed; switching when every group is idle"],
|
|
5405
5691
|
[/^正在切换到 v([\d.]+);期间的消息会提示稍后重发$/,"Switching to v$1; messages meanwhile are asked to be resent later"],
|
|
5406
5692
|
[/^已升级到 v([\d.]+)$/,"Upgraded to v$1"],
|
|
5693
|
+
[/^升级到 v([\d.]+)$/,"Upgrade to v$1"],
|
|
5694
|
+
[/^正在升级到 v([\d.]+)$/,"Upgrading to v$1"],
|
|
5695
|
+
[/^换回 v([\d.]+)$/,"Restore v$1"],
|
|
5696
|
+
[/^已用 (\d+) 秒$/,"Elapsed $1 s"],
|
|
5697
|
+
[/^已用 (\d+) 分 (\d+) 秒$/,"Elapsed $1 min $2 s"],
|
|
5698
|
+
[/^用时 (\d+) 秒$/,"Took $1 s"],
|
|
5699
|
+
[/^用时 (\d+) 分 (\d+) 秒$/,"Took $1 min $2 s"],
|
|
5700
|
+
[/^还在运行的群:([\s\S]+)$/,"Groups still working: $1"],
|
|
5701
|
+
[/^页面将在 (\d+) 秒后刷新,载入新版控制台$/,"Reloading in $1 s to load the new console"],
|
|
5407
5702
|
[/^没有切换到 v([\d.]+):([\s\S]*);已换回 v([\d.]+)$/,"Did not switch to v$1 ($2); v$3 was restored"],
|
|
5408
5703
|
[/^等待群空闲超时;可在空闲后重新发起$/,"Timed out waiting for idle groups; retry when they are idle"],
|
|
5409
5704
|
[/^有暂停的群,升级已取消;先处理暂停的群$/,"A group is paused, so the upgrade was cancelled; handle the paused group first"],
|
|
@@ -5611,7 +5906,17 @@ async function fetchCodexModels(){if(CX_MODELS)return CX_MODELS;try{CX_MODELS=aw
|
|
|
5611
5906
|
function cxDefEffortLabel(model){const m=CX_MODELS&&(CX_MODELS.models||[]).find(x=>x.model===model);return '默认'+(m&&m.defEffort?'('+m.defEffort+')':'');}
|
|
5612
5907
|
/* 有 codex provider 时启动即预热模型列表(server 缓存 10min)→ 卡片默认档标注补齐 + 面板秒开 */
|
|
5613
5908
|
function warmCodexModels(){if(!CX_MODELS&&S&&(S.providers||[]).some(x=>x.runner==='codex'&&!x.hidden))fetchCodexModels().then(()=>renderProviders());}
|
|
5614
|
-
const RUNTIME_DISABLED_SELECTORS='[data-osay]
|
|
5909
|
+
const RUNTIME_DISABLED_SELECTORS='[data-osay],.gpv,[data-rm],[data-gcfg],[data-cronnew],[data-cronadd],[data-crontgl],[data-cronrm],[data-reposave],[data-repotest],[data-iconuse],[data-iconsys],[data-iconstyle],[data-stylecollect],#mNewChat,#amDel,#mNewGo,#pgSend,#pgNew,#fSave,#slSave,#wSave,#bSave,#styleNew,[data-styleedit],[data-styledup],[data-styledel],#nsSave,#nsExtract,#clSave,[data-pvstop],[data-lvkill],[data-lvkillall]';
|
|
5910
|
+
// 持久处理流程按服务端白名单放行:前端照同一份名单判断某个动作能不能做,而不是一刀切。
|
|
5911
|
+
function runtimeAllows(method,path){
|
|
5912
|
+
const caps=S&&S.runtimeCapabilities;if(!caps)return true;
|
|
5913
|
+
return !!(caps.allowed&&(caps.allowed[method]||[]).includes(path));
|
|
5914
|
+
}
|
|
5915
|
+
// 持久处理流程下模型库写入的回执:默认模型被自动补上、有群开始了新的一段对话,都照实说出来。
|
|
5916
|
+
function runtimeChangeNote(pc){
|
|
5917
|
+
if(!pc)return '';
|
|
5918
|
+
return (pc.defaultProvider?' · 默认模型已改为 '+pc.defaultProvider:'')+(pc.resetGroups?' · '+pc.resetGroups+' 个群已开始新的一段对话':'');
|
|
5919
|
+
}
|
|
5615
5920
|
function disableRuntimeControl(el,reason){
|
|
5616
5921
|
el.disabled=true;el.title=trText(reason);el.dataset.runtimeDisabled='1';
|
|
5617
5922
|
el.setAttribute('aria-disabled','true');
|
|
@@ -5627,7 +5932,7 @@ function applyRuntimeCapabilities(){
|
|
|
5627
5932
|
if(!S||!S.runtimeCapabilities)return;
|
|
5628
5933
|
if(banner){const body=banner.querySelector('[data-runtime-summary]'),text=trText(RUNTIME_UI_TEXT.summary);if(body&&body.textContent!==text)body.textContent=text;}
|
|
5629
5934
|
document.querySelectorAll(RUNTIME_DISABLED_SELECTORS).forEach(el=>disableRuntimeControl(el,RUNTIME_UI_TEXT.unavailable));
|
|
5630
|
-
document.querySelectorAll('#pgrid .drag').forEach(el=>{el.draggable=false;el.title=trText(RUNTIME_UI_TEXT.unavailable);});
|
|
5935
|
+
if(!runtimeAllows('POST','/api/providers/order'))document.querySelectorAll('#pgrid .drag').forEach(el=>{el.draggable=false;el.title=trText(RUNTIME_UI_TEXT.unavailable);});
|
|
5631
5936
|
}
|
|
5632
5937
|
document.addEventListener('click',event=>{
|
|
5633
5938
|
if(!S||!S.runtimeCapabilities)return;
|