sintbot 1.0.7 → 1.0.9
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 +32 -0
- package/CHANGELOG.json +34 -0
- package/README.md +4 -8
- package/README.zh-CN.md +4 -6
- package/assets/design-styles/acid-pop.md +2 -0
- package/assets/design-styles/archive-index.md +2 -0
- package/assets/design-styles/art-deco.md +2 -0
- package/assets/design-styles/cinematic-noir.md +2 -0
- package/assets/design-styles/editorial.md +2 -0
- package/assets/design-styles/geometric-pop.md +2 -0
- package/assets/design-styles/hand-drawn.md +2 -0
- package/assets/design-styles/islamic-geometry.md +2 -0
- package/assets/design-styles/liquid-glass.md +2 -0
- package/assets/design-styles/neo-chinese.md +2 -0
- package/assets/design-styles/precision-mono.md +2 -0
- package/assets/design-styles/quiet-luxury.md +2 -0
- package/assets/design-styles/riso-print.md +2 -0
- package/assets/design-styles/soft-friendly.md +2 -0
- package/assets/design-styles/swiss-grid.md +2 -0
- package/assets/design-styles/tech-dark.md +2 -0
- package/bin/sbot.js +22 -10
- package/package.json +1 -1
- package/src/core/engine.js +2 -1
- package/src/core/proxy.js +19 -3
- package/src/design.js +219 -65
- package/src/platform/slack.js +1 -1
- package/src/ui/console.html +295 -37
- package/src/ui/dashboard.js +165 -0
- package/src/ui/server.js +98 -48
- package/src/ui/takeover.js +7 -0
package/src/ui/console.html
CHANGED
|
@@ -838,7 +838,9 @@ d.dataset.theme=t;if(l!=='en'&&l!=='zh')l=/^zh/i.test((navigator.language||''))?
|
|
|
838
838
|
<body>
|
|
839
839
|
<div class="app">
|
|
840
840
|
<div class="top">
|
|
841
|
-
|
|
841
|
+
<!-- 品牌字标不进翻译层(data-i18n-skip):「Sint」「Bot」是两个文本节点,英文界面下翻译层会把恰好等于某条译文的
|
|
842
|
+
英文反查回中文(i18nZhOrig),「机器人 → Bot」一加,切回中文时左上角就成了「Sint机器人」(10-24 发版前撞到)。 -->
|
|
843
|
+
<div class="mark"><a class="word" href="https://sintbot.com" target="_blank" rel="noopener" title="SintBot 官网" style="color:inherit;text-decoration:none"><span data-i18n-skip>Sint<i class="trace" aria-hidden="true"></i>Bot</span></a><span class="ver">__SBOT_VERSION__</span><small>控制台</small></div>
|
|
842
844
|
<div class="spacer"></div>
|
|
843
845
|
<div class="topright">
|
|
844
846
|
<!-- 当前连着的飞书应用(名称 + 图标)。左边 .mark 是产品品牌、固定不变;这块是「这台机器
|
|
@@ -999,8 +1001,8 @@ function renderOverview(){
|
|
|
999
1001
|
${liveOn?`<button class="iconbtn sm" data-ostop="${esc(g.chatId)}" title="停止当前任务(保留会话)" style="flex:none">${IC.square}</button>
|
|
1000
1002
|
<button class="iconbtn sm" data-onew="${esc(g.chatId)}" title="开新会话 — 运行中会先中断当前任务并清空队列(同群内 /new)" style="flex:none">${IC.rotate}</button>`:''}</div>`).join('')
|
|
1001
1003
|
:'<div class="empty">还没有群项目 · 去「群·项目」配置</div>';
|
|
1002
|
-
$('#overview').innerHTML=`<div class="grid cols2 stagger">
|
|
1003
|
-
<div class="card"><div class="lbl">健康</div>
|
|
1004
|
+
$('#overview').innerHTML=`<div id="ovInboxWrap">${inboxHtml()}</div><div class="grid cols2 stagger">
|
|
1005
|
+
<div class="card" id="ovHealth"><div class="lbl">健康</div>
|
|
1004
1006
|
<span class="hpill ${healthClass(h)}" id="ovHealthPill"><span class="led"></span><span id="ovHealthTxt">${healthText(h)}</span></span>
|
|
1005
1007
|
<div class="hrows" id="ovHrows">${h.rows.map(r=>`<div class="hrow" data-hk="${esc(r.label)}"><span class="dot ${r.status}"></span>
|
|
1006
1008
|
<span class="k">${esc(r.label)}</span><span class="v" title="${esc(r.detail)}">${esc(r.detail)}</span></div>`).join('')}</div>
|
|
@@ -1022,6 +1024,7 @@ function renderOverview(){
|
|
|
1022
1024
|
${leftoversHtml()}`;
|
|
1023
1025
|
lastActSig='';
|
|
1024
1026
|
bindActivityClicks();
|
|
1027
|
+
bindInbox();
|
|
1025
1028
|
// 插话:入口挂在「群·项目」每一行的 ✎ 上 —— 动作长在对象身上,目标群由那一行决定,不必再选。
|
|
1026
1029
|
$$('#overview [data-osay]').forEach(b=>b.onclick=()=>openSay(b.dataset.osay));
|
|
1027
1030
|
// 反馈本:标记已处理 / 放回未处理。备注会跟着发回原群 —— 提的人看到的是「修好了什么」,
|
|
@@ -1072,7 +1075,7 @@ function issuesHtml(){
|
|
|
1072
1075
|
${i.fp?`<div class="hint" style="flex-basis:100%;margin:2px 0 0">上一轮 ${esc(i.fp)}${i.version?' · v'+esc(i.version):''}</div>`:''}
|
|
1073
1076
|
${done&&i.note?`<div class="hint" style="flex-basis:100%;margin:2px 0 0">处理:${esc(i.note)}</div>`:''}
|
|
1074
1077
|
</div>`;
|
|
1075
|
-
return `<h2 class="sec">反馈本${open.length?` <span class="tag" style="color:var(--warn);border-color:var(--warn)">${open.length} 条待处理</span>`:''}</h2>
|
|
1078
|
+
return `<h2 class="sec" id="ovIssues">反馈本${open.length?` <span class="tag" style="color:var(--warn);border-color:var(--warn)">${open.length} 条待处理</span>`:''}</h2>
|
|
1076
1079
|
<div class="card ovgroups">${open.map(i=>row(i,false)).join('')}${closed.map(i=>row(i,true)).join('')}
|
|
1077
1080
|
<div class="hint" style="margin:10px 2px 0">群成员用 /issue 提的问题 · 标记已处理时会在原群回一句告诉他</div></div>`;
|
|
1078
1081
|
}
|
|
@@ -1094,7 +1097,7 @@ function previewsHtml(){
|
|
|
1094
1097
|
<a class="iconbtn sm" href="${esc(p.url)}" target="_blank" rel="noopener" title="在浏览器打开 ${esc(p.url)}" style="flex:none;text-decoration:none">${IC.ext}</a>
|
|
1095
1098
|
<button class="iconbtn sm" data-pvstop="${esc(p.id)}" data-pvname="${esc(p.label)}" title="停止这个预览(群里会收到告知)" style="flex:none">${IC.square}</button>
|
|
1096
1099
|
</div>`).join('');
|
|
1097
|
-
return `<h2 class="sec">预览服务</h2><div class="card ovgroups">${rows}
|
|
1100
|
+
return `<h2 class="sec" id="ovPreviews">预览服务</h2><div class="card ovgroups">${rows}
|
|
1098
1101
|
<div class="hint" style="margin:10px 2px 0">群里让它起的局域网原型预览 · 闲置到点自动停并在群里告知 · HTTP 明文,仅可信内网</div></div>`;
|
|
1099
1102
|
}
|
|
1100
1103
|
/* 后台进程(v0.11.108):agent 在对话轮次里用后台方式起的 shell,轮次结束后没跟着退、还在跑。
|
|
@@ -1112,13 +1115,59 @@ function leftoversHtml(){
|
|
|
1112
1115
|
<span class="mono faint" style="flex:none;font-size:11px" title="进程号">#${esc(p.pid)}</span>
|
|
1113
1116
|
<button class="iconbtn sm" data-lvkill="${esc(p.pid)}" data-lvage="${esc(p.age)}" title="结束这个后台进程" style="flex:none">${IC.x}</button>
|
|
1114
1117
|
</div>`).join('');
|
|
1115
|
-
return `<h2 class="sec">后台进程 <span class="tag" style="color:var(--warn);border-color:var(--warn)">${rows.length} 个还在跑</span></h2>
|
|
1118
|
+
return `<h2 class="sec" id="ovLeftovers">后台进程 <span class="tag" style="color:var(--warn);border-color:var(--warn)">${rows.length} 个还在跑</span></h2>
|
|
1116
1119
|
<div class="card ovgroups">${items}
|
|
1117
1120
|
<div class="inline" style="justify-content:space-between;align-items:center;margin:10px 2px 0;gap:12px">
|
|
1118
1121
|
<div class="hint" style="margin:0">对话轮次已经结束,这些由它起的后台命令却还在跑 —— 可能在反复打接口、占着端口或白耗资源;看一眼在干什么,不该留的就结束</div>
|
|
1119
1122
|
<button class="btn" data-lvkillall="1" title="结束上面列出的全部后台进程" style="flex:none;padding:5px 11px;font-size:12px">${IC.x} 全部结束</button>
|
|
1120
1123
|
</div></div>`;
|
|
1121
1124
|
}
|
|
1125
|
+
// 群名:活动事件只带 chatId 尾 8 位,这里按配置换成人认得的名字(群已解绑就退回尾号)
|
|
1126
|
+
function groupNameOf(chatId,fallback){const g=((S&&S.groups)||[]).find(x=>x.chatId===chatId);return (g&&g.name)||fallback||String(chatId||'').slice(-8);}
|
|
1127
|
+
// 「需要你处理」(汇总逻辑在 src/ui/dashboard.js):没事就不出现,有事放在概览最上面。
|
|
1128
|
+
// 只汇总、不另发通知 —— 群里该说的照旧在群里说。失败轮次可忽略;其余几类各有自己的处理区,点了滚过去。
|
|
1129
|
+
function inboxHtml(){
|
|
1130
|
+
const items=(S&&S.inbox)||[];
|
|
1131
|
+
if(!items.length) return '';
|
|
1132
|
+
const go=(id,tip)=>`<button class="iconbtn sm" data-ibgo="${id}" title="${tip}" style="flex:none">${IC.down}</button>`;
|
|
1133
|
+
const row=it=>{
|
|
1134
|
+
const g=it.chatId?groupNameOf(it.chatId,it.group):'';
|
|
1135
|
+
const line=(tag,col,title,act,more)=>`<div class="row" style="flex-wrap:wrap;align-items:baseline">
|
|
1136
|
+
<span class="tag" style="color:var(${col});border-color:var(${col})">${tag}</span>
|
|
1137
|
+
<span class="name" style="flex:none">${title}</span>
|
|
1138
|
+
<span class="meta" style="min-width:0">${[g?esc(g):'',it.ts?fmtWhen(it.ts):''].filter(Boolean).join(' · ')}</span>
|
|
1139
|
+
<span style="flex:1"></span>${act}${more||''}</div>`;
|
|
1140
|
+
const full=(html,extra)=>`<div style="flex-basis:100%;margin:3px 0 0;word-break:break-word${extra||''}">${html}</div>`;
|
|
1141
|
+
// 失败行与反馈本同形:谁 · 哪个群 · 几点,下面是原话和原因
|
|
1142
|
+
if(it.kind==='failed')return line('失败','--fail',esc(it.sender||'?'),
|
|
1143
|
+
`<button class="iconbtn sm" data-ibdis="${esc(it.key)}" title="忽略这一条(之后不再出现)" style="flex:none">${IC.x}</button>`,
|
|
1144
|
+
full(esc(it.text||''))+(it.err?full(IC.warn+' '+esc(it.err),';color:var(--fail)'):''));
|
|
1145
|
+
if(it.kind==='takeover')return line('接管','--warn','等人接管浏览器',
|
|
1146
|
+
`<a class="iconbtn sm" href="/browser" target="_blank" rel="noopener" title="打开接管页" style="flex:none">${IC.ext}</a>`,
|
|
1147
|
+
(it.pageTitle||it.pageUrl)?full(esc(it.pageTitle||it.pageUrl)):'');
|
|
1148
|
+
if(it.kind==='issues')return line('反馈','--warn',`${it.count} 条反馈待处理`,go('ovIssues','滚到反馈本'));
|
|
1149
|
+
if(it.kind==='leftovers')return line('进程','--warn',`${it.count} 个后台进程还在跑`,go('ovLeftovers','滚到后台进程'));
|
|
1150
|
+
if(it.kind==='preview')return line('预览','--fail','预览在服务整个项目目录',go('ovPreviews','滚到预览服务'));
|
|
1151
|
+
// 每个检查项单独一个节点:英文界面按整段文字逐个翻译
|
|
1152
|
+
if(it.kind==='health')return line('健康','--fail',`${it.count} 项健康检查没通过`,go('ovHealth','滚到健康'),
|
|
1153
|
+
full((it.labels||[]).map(l=>`<span>${esc(l)}</span>`).join(' · ')));
|
|
1154
|
+
return '';
|
|
1155
|
+
};
|
|
1156
|
+
return `<h2 class="sec" id="ovInbox" style="margin-top:0">需要你处理 <span class="tag" style="color:var(--warn);border-color:var(--warn)">${items.length}</span></h2>
|
|
1157
|
+
<div class="card ovgroups" style="margin-bottom:22px">${items.map(row).join('')}</div>`;
|
|
1158
|
+
}
|
|
1159
|
+
function bindInbox(){
|
|
1160
|
+
$$('#overview [data-ibgo]').forEach(b=>b.onclick=()=>{const el=$('#'+b.dataset.ibgo);if(el)el.scrollIntoView({behavior:'smooth',block:'start'});});
|
|
1161
|
+
$$('#overview [data-ibdis]').forEach(b=>b.onclick=async()=>{
|
|
1162
|
+
try{const r=await api('/api/inbox/dismiss','POST',{key:b.dataset.ibdis});S.inbox=r.inbox||[];paintInbox();toast('已忽略','ok');}catch(e){toast(e.message,'err');}});
|
|
1163
|
+
}
|
|
1164
|
+
// 只重画收件箱这一块(轮次结束 / 忽略之后):概览其它区不动,展开着的活动行和滚动位置都不丢。
|
|
1165
|
+
// 签名跟着重算 ⇒ 下一次例行刷新不会因为这块变了再把整页重画一遍。
|
|
1166
|
+
function paintInbox(){const w=$('#ovInboxWrap');if(!w)return;w.innerHTML=inboxHtml();bindInbox();lastStateSig=stateSig(S);tabSig.overview=lastStateSig;}
|
|
1167
|
+
async function refreshInbox(){
|
|
1168
|
+
if(!S||!TOKEN||document.visibilityState!=='visible')return;
|
|
1169
|
+
try{const r=await api('/api/inbox');S.inbox=r.inbox||[];paintInbox();}catch{/* 下一次例行刷新会补上 */}
|
|
1170
|
+
}
|
|
1122
1171
|
function activityHtml(){
|
|
1123
1172
|
if(!activity.length) return '<div class="empty">服务启动后显示实时活动</div>';
|
|
1124
1173
|
return activity.slice(0,10).map(a=>{const run=a.status==='running';
|
|
@@ -1127,11 +1176,14 @@ function activityHtml(){
|
|
|
1127
1176
|
const k=a.kind||'';const col=run?'color:var(--ember)':((k==='error'||k==='failed')?'color:var(--fail)':(k==='empty'?'color:var(--warn)':''));
|
|
1128
1177
|
const tok=a.tokens?` <span class="faint">${IC.down}${fmtTok(a.tokens.out)}</span>`:'';
|
|
1129
1178
|
const st=run?'<span class="spin"></span> running':(statusIcon(k)+' '+esc(a.sec||'')).trim();
|
|
1130
|
-
|
|
1179
|
+
// 出错的行直接把原因摆出来(与账本同一句);其余照旧显示回复摘要
|
|
1180
|
+
const bad=k==='error'||k==='failed';
|
|
1181
|
+
const prev=(bad&&a.err&&!a.open)?`<div class="apreview" style="color:var(--fail)">${IC.warn} ${esc(a.err)}</div>`
|
|
1182
|
+
:((a.preview&&!run&&!a.open)?`<div class="apreview">${IC.corner} ${esc(a.preview)}</div>`:'');
|
|
1131
1183
|
const exp=a.open?activityExpand(a):'';
|
|
1132
1184
|
return `<div class="arow"><div class="aline" data-aid="${esc(a.id)}">
|
|
1133
1185
|
<span class="num faint">${esc(a.t||'')}</span>
|
|
1134
|
-
<span class="name" style="font-size:12px;font-weight:500">${esc(a.group
|
|
1186
|
+
<span class="name" style="font-size:12px;font-weight:500">${esc(groupNameOf(a.chatId,a.group))}</span>
|
|
1135
1187
|
<span class="meta" style="flex:1;min-width:0">${esc(a.text||'')}</span>
|
|
1136
1188
|
<span class="meta num" style="${col}">${st}${tok}</span></div>${prev}${exp}</div>`;}).join('');
|
|
1137
1189
|
}
|
|
@@ -1142,6 +1194,7 @@ function activityExpand(a){
|
|
|
1142
1194
|
const meta=[];
|
|
1143
1195
|
if(a.sender)meta.push(`<span><b>sender</b> ${esc(a.sender)}</span>`);
|
|
1144
1196
|
if(a.status)meta.push(`<span><b>status</b> ${esc(runLabel(a.status))}</span>`);
|
|
1197
|
+
if(a.err)meta.push(`<span><b>原因</b> ${esc(a.err)}</span>`);
|
|
1145
1198
|
if(a.ms)meta.push(`<span><b>耗时</b> ${(a.ms/1000).toFixed(1)}s</span>`);
|
|
1146
1199
|
// ↑ = 输入侧总量(未命中 + 缓存读写,v0.11.17 起口径归一);有缓存字段时括号里给拆分
|
|
1147
1200
|
if(a.tokens){const cr=a.tokens.cr||0,cw=a.tokens.cw||0;meta.push(`<span><b>tokens</b> ${IC.upArrow}${fmtTok((a.tokens.in||0)+cr+cw)} ${IC.down}${fmtTok(a.tokens.out)}${(cr||cw)?` <span class="faint">(缓存读 ${fmtTok(cr)} · 写 ${fmtTok(cw)})</span>`:''}</span>`);}
|
|
@@ -1207,11 +1260,12 @@ let autoTimer=null,lastStateSig=null;
|
|
|
1207
1260
|
打断)。而这些数字是用 fmtDur 按**分钟**显示的 ⇒ 折成分钟后,签名恰好在显示文字会变时才变。
|
|
1208
1261
|
live/conn/time 整块剔掉(它们走 3s 心跳,进签名会每帧变)。 */
|
|
1209
1262
|
function stateSig(s){try{
|
|
1210
|
-
const {live,conn,time,previews,...rest}=s||{};
|
|
1263
|
+
const {live,conn,time,previews,inbox,...rest}=s||{};
|
|
1211
1264
|
const pv=(previews||[]).map(p=>[p.id,p.port,p.label,p.serveRoot,p.projectRoot,p.reqCount,
|
|
1212
1265
|
Math.round((p.upMs||0)/60000),p.lastReqMs==null?null:Math.round(p.lastReqMs/60000),
|
|
1213
1266
|
p.idleLeftMs==null?null:Math.round(p.idleLeftMs/60000)]);
|
|
1214
|
-
|
|
1267
|
+
const ib=(inbox||[]).map(i=>[i.key,i.count||0]); // 时间戳每次刷新都在走(进程年龄、预览时长),只签身份
|
|
1268
|
+
return JSON.stringify([rest,pv,ib]);
|
|
1215
1269
|
}catch{return null;}}
|
|
1216
1270
|
function busyInteracting(){
|
|
1217
1271
|
if($('#modalBg')&&$('#modalBg').classList.contains('show'))return true; // 模态框开着
|
|
@@ -1849,9 +1903,25 @@ let styleLib=null;
|
|
|
1849
1903
|
async function loadStyleLib(){
|
|
1850
1904
|
const box=$('#styleLib'); if(!box)return;
|
|
1851
1905
|
if(styleLib){paintStyleLib();return;}
|
|
1852
|
-
try{const _r=await api('/api/design/styles');styleLib=_r;paintStyleLib();}
|
|
1906
|
+
try{const _r=await api('/api/design/styles');styleLib=_r;registerStyleI18n(_r.styles);paintStyleLib();}
|
|
1853
1907
|
catch(e){box.innerHTML=`<div class="card"><div class="hint">风格库读取失败:${esc(e.message)}</div></div>`;}
|
|
1854
1908
|
}
|
|
1909
|
+
/* 风格的英文名与一句话写在各自的风格档里(name_en / description_en),这里登记进翻译表 ——
|
|
1910
|
+
卡片照旧按中文渲染,英文界面由那套 MutationObserver 逐节点换掉,切回中文也是同一条路。
|
|
1911
|
+
「适合 · 内容阅读 · 品牌门面」那行按 · 分段逐段译,场景词的译文在 I18N_EN 里。 */
|
|
1912
|
+
function registerStyleI18n(list){
|
|
1913
|
+
for(const s of list||[]){
|
|
1914
|
+
if(s.name&&s.nameEn&&s.name!==s.nameEn)I18N_MAP.set(s.name,s.nameEn);
|
|
1915
|
+
if(s.description&&s.descriptionEn&&s.description!==s.descriptionEn)I18N_MAP.set(s.description,s.descriptionEn);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
/* 启动后在后台登记一次:群卡上的项目风格名多半照抄自内置预设,英文界面里也要换掉 —— 不能等人点开「模型 · 库」才登记。
|
|
1919
|
+
只登记一次(styleLib 已有就跳过);取不到就算了,风格库那页打开时还会再取。 */
|
|
1920
|
+
let styleI18nAsked=false;
|
|
1921
|
+
function preloadStyleI18n(){
|
|
1922
|
+
if(styleI18nAsked||styleLib)return; styleI18nAsked=true;
|
|
1923
|
+
api('/api/design/styles').then(r=>{if(!styleLib)styleLib=r;registerStyleI18n(r.styles);if(langOf()==='en')applyI18n(document.body);}).catch(()=>{styleI18nAsked=false;});
|
|
1924
|
+
}
|
|
1855
1925
|
function paintStyleLib(){
|
|
1856
1926
|
const box=$('#styleLib'); if(!box||!styleLib)return;
|
|
1857
1927
|
// 特征标签只标「有性格」的信号:衬线/深色/等宽/圆角值 —— 「无衬线」「直角」是大多数的常态,
|
|
@@ -2039,14 +2109,18 @@ function nsPreview(){
|
|
|
2039
2109
|
}catch(e){const el=$('#nsPrev'); if(el)el.innerHTML='<span class="hint">预览暂时出不来</span>';}
|
|
2040
2110
|
},240);
|
|
2041
2111
|
}
|
|
2042
|
-
/* 提炼回填:只覆盖「参考里读得出来」的那几项(
|
|
2043
|
-
那两项是人写的,被一次提炼冲掉最恼人。
|
|
2044
|
-
|
|
2112
|
+
/* 提炼回填:只覆盖「参考里读得出来」的那几项(色板/圆角),名称与描述留着不动 ——
|
|
2113
|
+
那两项是人写的,被一次提炼冲掉最恼人。
|
|
2114
|
+
⚠ 读不到的项**不动表单**:一个颜色都没读到时 form.colors 是模板默认值、没读到圆角时 rounded 是兜底的「微圆」,
|
|
2115
|
+
照填就是把默认值当成提炼结果,还冲掉人已经选好的(实测 linear.app)。字重提炼器根本不读,
|
|
2116
|
+
只有目录里本来就有风格档(styledoc)时才是真值。 */
|
|
2117
|
+
function nsFill(f,ev){
|
|
2045
2118
|
if(!f)return;
|
|
2046
|
-
|
|
2119
|
+
const e=ev||{};
|
|
2120
|
+
if(e.colorsFound!==false)for(const [k] of NS_CKEYS){const v=nsNormHex((f.colors||{})[k]);if(!v)continue;
|
|
2047
2121
|
$('#nsCol_'+k).value=v;$('#nsHex_'+k).value=v;}
|
|
2048
|
-
$$('#nsRound button').forEach(b=>b.classList.toggle('on',b.dataset.v===f.rounded));
|
|
2049
|
-
$$('#nsWeight button').forEach(b=>b.classList.toggle('on',b.dataset.v===f.weight));
|
|
2122
|
+
if(e.roundedFound!==false&&f.rounded)$$('#nsRound button').forEach(b=>b.classList.toggle('on',b.dataset.v===f.rounded));
|
|
2123
|
+
if((!ev||e.source==='styledoc')&&f.weight)$$('#nsWeight button').forEach(b=>b.classList.toggle('on',b.dataset.v===f.weight));
|
|
2050
2124
|
/* ⚠ 字体不在表单里,提炼回填也就无从谈起(提色器本来也读不出中文族) */
|
|
2051
2125
|
nsHexCheck();
|
|
2052
2126
|
}
|
|
@@ -2110,9 +2184,10 @@ function nsEvidence(ev){
|
|
|
2110
2184
|
else if(ev.source==='dir')rows.push('扫了 '+(ev.files||0)+' 个样式文件');
|
|
2111
2185
|
const sw=(ev.colors||[]).map(c=>`<span class="sw" style="background:${esc(c.hex)}"></span>${esc(c.hex)}${c.count?'<span class="faint"> ×'+c.count+'</span>':(c.role?'<span class="faint"> '+esc(c.role)+'</span>':'')}`).join(' ');
|
|
2112
2186
|
if(sw)rows.push('提到最多的颜色 · '+sw);
|
|
2187
|
+
else if(ev.colorsFound===false)rows.push('没读到颜色,四个颜色保持原样');
|
|
2113
2188
|
if((ev.fonts||[]).length)rows.push('字体族 · '+ev.fonts.map(f=>esc(f)).join(' / '));
|
|
2114
2189
|
if((ev.radii||[]).length)rows.push('圆角取值 · '+ev.radii.join(' / ')+'px');
|
|
2115
|
-
else if(ev.roundedFound===false)rows.push('
|
|
2190
|
+
else if(ev.roundedFound===false)rows.push('没读到圆角设定,圆角保持原样');
|
|
2116
2191
|
const ts=nsTokensSummary(nsTokens);
|
|
2117
2192
|
if(ts)rows.push('还读到 · '+ts);
|
|
2118
2193
|
/* 三样属性也要如实报(v0.11.22):它们已经把表单上的档位改掉了,不说一声就是「悄悄替人做主」。
|
|
@@ -2122,7 +2197,7 @@ function nsEvidence(ev){
|
|
|
2122
2197
|
if(tr.elevation)trs.push('投影 '+({none:'一处都没有 ⇒ 无',flat:'很浅 ⇒ 轻',soft:'明显',color:'带颜色 ⇒ 彩色'}[tr.elevation]||tr.elevation));
|
|
2123
2198
|
if(tr.gradientTo)trs.push('渐变到 <span class="sw" style="background:'+esc(tr.gradientTo)+'"></span>'+esc(tr.gradientTo));
|
|
2124
2199
|
if(tr.statusColors)trs.push('状态色 '+Object.keys(tr.statusColors).map(k=>({success:'成功',warning:'警告',danger:'危险'}[k]||k)+' <span class="sw" style="background:'+esc(tr.statusColors[k])+'"></span>').join(' '));
|
|
2125
|
-
if(trs.length)rows.push('还读到 · '+trs.join(' · ')+'<span class="faint">(没提到的那几样保持原样)</span>');
|
|
2200
|
+
if(trs.length)rows.push('还读到 · '+trs.join(' · ')+' <span class="faint">(没提到的那几样保持原样)</span>');
|
|
2126
2201
|
if(nsExtra.length)rows.push('补充色(勾上的会一起写进这套风格)· '+nsChipsHtml('nsExtraChips'));
|
|
2127
2202
|
if(nsSug&&nsSug.description)rows.push('这一句只说事实,可以直接当描述 · <span class="dim">'+esc(nsSug.description)
|
|
2128
2203
|
+'</span> <button type="button" class="btn nsmini" id="nsUseDesc">用这句</button>');
|
|
@@ -2260,7 +2335,7 @@ function openStyleModal(form,opt){
|
|
|
2260
2335
|
if(!ref){toast('先填一个网址,或本机上项目所在目录的绝对路径','err');return;}
|
|
2261
2336
|
const b=$('#nsExtract');b.disabled=true;const old=b.textContent;b.innerHTML='<span class="spin"></span>';
|
|
2262
2337
|
/* 顺序要紧:先把建议收进状态(nsApplySuggest),证据区才画得出补充色勾选与那句描述 */
|
|
2263
|
-
try{const r=await api('/api/design/extract','POST',{ref});nsFill(r.form);nsFillTraits(r.suggest&&r.suggest.traits);nsApplySuggest(r.suggest);nsEvidence(r.evidence);nsPreview();
|
|
2338
|
+
try{const r=await api('/api/design/extract','POST',{ref});nsFill(r.form,r.evidence);nsFillTraits(r.suggest&&r.suggest.traits);nsApplySuggest(r.suggest);nsEvidence(r.evidence);nsPreview();
|
|
2264
2339
|
toast('已给出一套起点 —— 逐项改到满意为止','ok');}
|
|
2265
2340
|
catch(e){toast(e.message,'err');}
|
|
2266
2341
|
finally{if(document.body.contains(b)){b.disabled=false;b.textContent=old;}}
|
|
@@ -3617,7 +3692,7 @@ $$('nav button').forEach(b=>b.onclick=()=>{
|
|
|
3617
3692
|
});
|
|
3618
3693
|
|
|
3619
3694
|
async function loadUsage(){try{usage=await api('/api/usage');renderProviders();}catch(e){}}
|
|
3620
|
-
async function load(){try{S=await api('/api/state');seedCodexStatus();await syncActivity(false);renderAll();startClock();loadUsage();autoVerifyCodex();autoVerifyClaude();warmCodexModels();}catch(e){if(e.message==='需要登录')return;$('#overview').innerHTML='<div class="empty" style="padding:48px;text-align:center;color:var(--fail)">✕ 加载失败:'+esc(e.message)+'<br><br><span class="faint">确认后台服务正在运行</span></div>';toast('加载失败:'+e.message,'err');}}
|
|
3695
|
+
async function load(){try{S=await api('/api/state');seedCodexStatus();await syncActivity(false);renderAll();startClock();loadUsage();autoVerifyCodex();autoVerifyClaude();warmCodexModels();preloadStyleI18n();}catch(e){if(e.message==='需要登录')return;$('#overview').innerHTML='<div class="empty" style="padding:48px;text-align:center;color:var(--fail)">✕ 加载失败:'+esc(e.message)+'<br><br><span class="faint">确认后台服务正在运行</span></div>';toast('加载失败:'+e.message,'err');}}
|
|
3621
3696
|
$('#refresh').onclick=()=>{load();toast('已刷新','ok');};
|
|
3622
3697
|
/* 主题切换:dark(默认,Mocha 调暗)↔ light(Catppuccin Latte)。独立于 renderAll —— 切主题不重渲染卡片。 */
|
|
3623
3698
|
function themeOf(){return document.documentElement.dataset.theme||'dark';}
|
|
@@ -3898,10 +3973,11 @@ const I18N_EN=[
|
|
|
3898
3973
|
["新建风格」配一套自己的(比如把公司品牌色配成一款)· 已经在某个项目里打磨好的那份,可在「群·项目」页那个群的「设计风格」行收录进来","New style\" creates one of your own (e.g. your company brand color as a style) · a style already polished in a project can be collected from the \"Design style\" row of that group on the Groups · Projects page"],
|
|
3899
3974
|
["已移除 · 同名的内置风格重新可用","Removed · the built-in style with the same identifier is available again"],
|
|
3900
3975
|
["已从风格库移除","Removed from the style library"],
|
|
3901
|
-
["
|
|
3976
|
+
["适合","Good for"],
|
|
3977
|
+
["内容阅读","Reading"],
|
|
3902
3978
|
["品牌门面","Brand showcase"],
|
|
3903
|
-
["工具产品","
|
|
3904
|
-
["活动推广","
|
|
3979
|
+
["工具产品","Product UI"],
|
|
3980
|
+
["活动推广","Campaigns"],
|
|
3905
3981
|
["生图插画","Image & illustration"],
|
|
3906
3982
|
["小程序","Mini program"],
|
|
3907
3983
|
["后台网站","Admin site"],
|
|
@@ -3935,7 +4011,8 @@ const I18N_EN=[
|
|
|
3935
4011
|
["次要不是合法的颜色值 —— 写成 #RRGGBB 或 #RGB","Muted is not a valid color — use #RRGGBB or #RGB"],
|
|
3936
4012
|
["预览暂时出不来","Preview unavailable right now"],
|
|
3937
4013
|
["提到最多的颜色 ·","Most-used colors ·"],
|
|
3938
|
-
["
|
|
4014
|
+
["没读到圆角设定,圆角保持原样","No radius setting found; the radius stays as it was"],
|
|
4015
|
+
["没读到颜色,四个颜色保持原样","No colors found; the four colors stay as they were"],
|
|
3939
4016
|
["还读到","Also read"],
|
|
3940
4017
|
["投影 一处都没有 ⇒ 无","Shadow: none anywhere ⇒ None"],
|
|
3941
4018
|
["投影 很浅 ⇒ 轻","Shadow: very faint ⇒ Light"],
|
|
@@ -4310,6 +4387,27 @@ const I18N_EN=[
|
|
|
4310
4387
|
["退出所有设备","Sign out everywhere"],
|
|
4311
4388
|
["版本历史","Version history"],
|
|
4312
4389
|
["关于 SintBot","About SintBot"],
|
|
4390
|
+
["需要你处理","Needs you"],
|
|
4391
|
+
["接管","Takeover"],
|
|
4392
|
+
["等人接管浏览器","Waiting for someone to take over the browser"],
|
|
4393
|
+
["打开接管页","Open the takeover page"],
|
|
4394
|
+
["反馈","Feedback"],
|
|
4395
|
+
["进程","Process"],
|
|
4396
|
+
["预览在服务整个项目目录","A preview is serving the whole project directory"],
|
|
4397
|
+
["忽略这一条(之后不再出现)","Dismiss (it won't come back)"],
|
|
4398
|
+
["已忽略","Dismissed"],
|
|
4399
|
+
["滚到反馈本","Go to Feedback log"],
|
|
4400
|
+
["滚到后台进程","Go to Background processes"],
|
|
4401
|
+
["滚到预览服务","Go to Preview servers"],
|
|
4402
|
+
["滚到健康","Go to Health"],
|
|
4403
|
+
["可用模型","Usable models"],
|
|
4404
|
+
["可用群 / 项目","Groups / projects"],
|
|
4405
|
+
["飞书凭据","Feishu credentials"],
|
|
4406
|
+
["Slack 凭据","Slack credentials"],
|
|
4407
|
+
["无可用模型 — 去「模型 · 库」页添加 / 测试","No usable model — add or test one on the Models · Library tab"],
|
|
4408
|
+
["无绑定群/项目 — 去「群·项目」页创建","No group bound yet — create one on the Groups · Projects tab"],
|
|
4409
|
+
["原因","Reason"],
|
|
4410
|
+
["后台进程","Background processes"],
|
|
4313
4411
|
["SintBot 是在聊天群里干活的自托管 AI 助手:一个群绑一个项目,无头驱动 Claude Code 或 Codex,模型随时换、项目记忆不丢,全程在你自己的机器上。","SintBot is a self-hosted AI assistant that works from your group chats: one group per project, headless Claude Code or Codex, any model behind it, and one project memory that survives model switches — all on your own machine."],
|
|
4314
4412
|
["版本","Version"],
|
|
4315
4413
|
["官网","Website"],
|
|
@@ -4395,7 +4493,7 @@ const I18N_EN=[
|
|
|
4395
4493
|
["开放平台","Open platform"],
|
|
4396
4494
|
["自建应用","custom app"],
|
|
4397
4495
|
["→ 「添加应用能力」加上"," → under \"Add app capability\" add a "],
|
|
4398
|
-
["机器人","
|
|
4496
|
+
["机器人","Bot"],
|
|
4399
4497
|
["(否则拉不进群、收不到消息)。建应用时"," (otherwise it cannot be pulled into groups and receives no messages). When creating the app, an "],
|
|
4400
4498
|
["图标必填","icon is required"],
|
|
4401
4499
|
[",选一个内置图标即可。下面这套 scope 在开放平台里全部标「免审权限」,自建应用发版也免审(创建者即企业管理员时自动通过)——","; any built-in icon will do. Every scope below is marked \"no-review permission\" on the open platform, and publishing a custom app needs no review either (auto-approved when the creator is the org admin) — "],
|
|
@@ -4554,8 +4652,160 @@ const I18N_EN=[
|
|
|
4554
4652
|
["只列了机器人已加入的频道 —— 手填的频道 ID 若机器人不在频道里,绑了也收不到消息","Only channels the bot has joined are listed — if you type a channel ID the bot isn't in, binding it won't receive any messages"],
|
|
4555
4653
|
["Slack 频道:没有画布与表情回应,群内收发文件未接;控制面用 !命令 或 /sint","Slack channel: no canvas or reactions, in-channel file transfer not connected yet; use !commands or /sint for controls"],
|
|
4556
4654
|
["企业微信群:只收 @ 机器人的消息;没有画布、表情回应与撤回即停","WeCom group: only receives messages that @ the bot; no canvas, reactions or stop-on-recall"],
|
|
4655
|
+
// 10-24 英文界面核对补齐:企业微信卡、健康明细、群卡输入提示、飞书登录未就绪原因、企业微信预检结果
|
|
4656
|
+
["GPT 订阅", "GPT subscription"],
|
|
4657
|
+
["已启用", "Enabled"],
|
|
4658
|
+
["每小时整点", "Every hour on the hour"],
|
|
4659
|
+
["未填公网地址", "no public address set"],
|
|
4660
|
+
["未配置飞书应用凭证", "Feishu app credentials are not set"],
|
|
4661
|
+
["管理员名单为空(只有名单里的人能登录)", "the admin list is empty (only people on it can sign in)"],
|
|
4662
|
+
["配置文件损坏,先修好它", "the config file is damaged; fix it first"],
|
|
4663
|
+
["如何在企业微信里开通", "How to set it up in WeCom"],
|
|
4664
|
+
["电脑端客户端「", "In the desktop client, open \""],
|
|
4665
|
+
["工作台 → 智能机器人 → 创建机器人 → 手动创建 → API 模式", "Workbench → Smart bots → Create bot → Create manually → API mode"],
|
|
4666
|
+
["」(手机端不能建;管理后台「安全与管理 → 管理工具 → 智能机器人」是同一个入口)。填名称 / 可见范围;", "\" (the mobile app can't create one; \"Security & Management → Management tools → Smart bots\" in the admin console is the same entry). Fill in the name and visibility, and "],
|
|
4667
|
+
["使用方式选「多人使用」", "set usage to \"Multi-person\""],
|
|
4668
|
+
["不需要企业认证", "No company verification is needed"],
|
|
4669
|
+
[",免费注册的企业就能建。", ": any freely registered company can create one."],
|
|
4670
|
+
["API 配置页选「", "On the API settings page choose \""],
|
|
4671
|
+
["使用长连接", "Use long connection"],
|
|
4672
|
+
["」(免公网 / 域名 / 可信 IP),记下", "\" (no public address, domain or trusted IP needed), then copy the"],
|
|
4673
|
+
["与", "and"],
|
|
4674
|
+
["填到下面。保存时会弹一次使用规则确认。", "into the fields below. Saving asks you once to confirm the usage rules. "],
|
|
4675
|
+
["一个机器人只许一条长连接", "One bot allows only one long connection"],
|
|
4676
|
+
["—— 新连上的会把旧的踢下线,所以每台服务器各建各的机器人,别共用。", "— a new connection kicks the old one off, so create a separate bot for each server instead of sharing one."],
|
|
4677
|
+
["服务器上装官方命令行工具并用", "Install the official command-line tool on the server and authorize it with the "],
|
|
4678
|
+
["同一对 Bot ID / Secret", "same Bot ID / Secret"],
|
|
4679
|
+
["授权一次 —— 群里显示成员姓名、助手发图发文件、这里列出可绑定的群都靠它;没装只降级不报错。", " once — member names in groups, sending images and files, and listing the groups you can bind here all depend on it. Without it those features are simply off, with no errors."],
|
|
4680
|
+
["在企业微信里", "In WeCom, "],
|
|
4681
|
+
["建群 → 从群菜单把机器人加进群 → @ 它一次", "create a group → add the bot from the group menu → @ it once"],
|
|
4682
|
+
[",再回「群·项目 → 配置群项目」选那个群。机器人没有建群 / 改名 / 解散的接口,这几步只能在企业微信里做;群里", ", then come back to \"Groups · Projects → Set up a group project\" and pick that group. The bot has no API to create, rename or disband groups, so these steps happen in WeCom. In a group it receives "],
|
|
4683
|
+
["只有 @ 机器人的消息", "only messages that @ the bot"],
|
|
4684
|
+
["它才收得到。", "."],
|
|
4685
|
+
["换了 Bot ID / Secret 之后必须重启守护进程", "After changing the Bot ID / Secret, restart the daemon"],
|
|
4686
|
+
["—— 长连接在启动时就绑死了凭据,这里保存只是热重载配置、不会重建连接。", "— the long connection binds its credentials at startup; saving here hot-reloads the config but does not rebuild the connection."],
|
|
4687
|
+
["凭据", "Credentials"],
|
|
4688
|
+
["预检", "Pre-check"],
|
|
4689
|
+
["核对凭据形状、命令行工具是否装好并授权、能列出几个可绑定的群(不另开连接)", "Checks the credential format, whether the command-line tool is installed and authorized, and how many groups can be bound (opens no extra connection)"],
|
|
4690
|
+
["身份与连接", "Identity and connection"],
|
|
4691
|
+
["未连接", "Not connected"],
|
|
4692
|
+
["长连接在线", "Long connection online"],
|
|
4693
|
+
["(未知)", "(unknown)"],
|
|
4694
|
+
["Bot ID 未填", "Bot ID not set"],
|
|
4695
|
+
["守护进程没有接企业微信(没起、或配置改了还没重启)", "The daemon isn't connected to WeCom (not running, or the config changed and it hasn't restarted)"],
|
|
4696
|
+
["已订阅", "Subscribed"],
|
|
4697
|
+
["已连上但还没订阅成功", "Connected but not subscribed yet"],
|
|
4698
|
+
["已存;点「刷新身份」可随时核对", "Saved; click \"Refresh identity\" to check it any time"],
|
|
4699
|
+
["还不知道它叫什么 —— 点「刷新身份」从命令行工具的身份接口取(没装工具时在群里 @ 它一句也能学到)", "Its name isn't known yet — click \"Refresh identity\" to read it from the command-line tool (without the tool, @ it once in a group and it learns the name)"],
|
|
4700
|
+
["刚从群里的 @ 学到,点「刷新身份」存下", "Just learned from an @ in a group; click \"Refresh identity\" to save it"],
|
|
4701
|
+
["扫最近几个群的历史,被 @ 最多的名字就是它", "Scans recent group history; the name @-mentioned most is the bot's"],
|
|
4702
|
+
["单聊欢迎语", "Direct-chat welcome"],
|
|
4703
|
+
["— 成员首次打开单聊时回的一句;留空不回", "— sent when a member first opens a direct chat; leave empty to send nothing"],
|
|
4704
|
+
["你好,我是这个项目的助手…", "Hi, I'm this project's assistant…"],
|
|
4705
|
+
["成员显示名", "Member display names"],
|
|
4706
|
+
["— 每行「账号 显示名」;企业微信只给账号,命令行工具装好后会自动查姓名,这里填的优先", "— one \"account name\" pair per line. WeCom only provides accounts; once the command-line tool is installed it looks names up, and names here take priority"],
|
|
4707
|
+
["ZhangSan 张三", "zhangsan Zhang San"],
|
|
4708
|
+
["不 @ 也回复", "Reply without @"],
|
|
4709
|
+
["不 @ 也回复 开", "Reply without @: on"],
|
|
4710
|
+
["默认关", "Off by default"],
|
|
4711
|
+
[":企业微信的设计就是 @ 机器人才答,只推 @ 的消息。开了这项后每隔几秒拉一次群历史来补没 @ 的消息 —— 官方使用规则对自动化抓取和异常频率有限制,超过 10 人的企业官方还没开放这条接口,所以只建议小的测试群开。开着时:被 @ 或回复后一段时间内的消息、点了机器人名字的消息不 @ 也答;", ": WeCom is designed so the bot answers only when @-mentioned, and it only pushes @ messages. With this on, the daemon pulls group history every few seconds to catch messages without an @. The official usage rules limit automated fetching and unusual request rates, and WeCom hasn't opened this API to companies with more than 10 people yet, so turn it on only for small test groups. While it is on, the bot also answers without an @ for a while after an @ or a reply, and when a message names it; "],
|
|
4712
|
+
["真正耗额度的是喂给模型的每一条", "what really costs quota is every message fed to the model"],
|
|
4713
|
+
[",默认只在「跟进窗口」内喂。", ", and by default only messages inside the follow-up window are fed."],
|
|
4714
|
+
["看群历史的间隔(秒)", "Group history interval (seconds)"],
|
|
4715
|
+
["默认 0 = 关(只答 @ 的)· 要开填 20 左右 —— 这是平时的间隔:刚聊过时 8 秒一看,久没人说话时 2 分钟一看;@ 它的消息不走这条,随时即答", "Default 0 = off (answers @ only) · to turn it on, enter about 20 — the normal interval: every 8 seconds right after a conversation, every 2 minutes when the group has been quiet. Messages that @ it skip this and are answered right away"],
|
|
4716
|
+
["跟进窗口(秒)", "Follow-up window (seconds)"],
|
|
4717
|
+
["默认 300 · 0 = 群里每一句都喂模型(很烧额度)", "Default 300 · 0 = feed every group message to the model (uses a lot of quota)"],
|
|
4718
|
+
["拿不准的先问一个便宜模型", "Ask a cheap model when unsure"],
|
|
4719
|
+
["不问(只按上面两条规则)", "Don't ask (use only the two rules above)"],
|
|
4720
|
+
["(已隐藏或缺密钥,仍按配置生效)", "(hidden or missing a key; still applied from the config)"],
|
|
4721
|
+
["没点名、也不在跟进窗口里的消息,先让它判一句「机器人该不该接」(每条只花几十 token、不带会话),说要才喂真正的模型。只能选配了接口地址和密钥的模型。", "For messages that don't name the bot and fall outside the follow-up window, it first asks this model whether the bot should answer (a few dozen tokens each, no session), and feeds the real model only if the answer is yes. Only models with an endpoint and a key can be chosen."],
|
|
4722
|
+
["门控用的型号", "Gate model"],
|
|
4723
|
+
["— 可选,留空用该模型的默认型号;建议填它最便宜的那款", "— optional; empty uses that provider's default model. Its cheapest model is a good choice"],
|
|
4724
|
+
["留空 = 默认型号", "Empty = default model"],
|
|
4725
|
+
["点名词", "Call words"],
|
|
4726
|
+
["— 群消息里出现这些词(或机器人名)就当是在叫它,不 @ 也答;逗号分隔;留空 = 「机器人、助手」", "— a group message containing one of these words (or the bot's name) counts as calling it and gets an answer without an @. Comma-separated; empty = \"机器人, 助手\" (bot, assistant)"],
|
|
4727
|
+
["命令行工具", "Command-line tool"],
|
|
4728
|
+
["成员姓名、助手发图发文件、列出可绑定的群、「不 @ 也回复」都靠服务器上装好并授权的官方命令行工具;没装只降级不报错(预检会说)。", "Member names, sending images and files, listing bindable groups and \"Reply without @\" all depend on the official command-line tool being installed and authorized on the server. Without it those features are simply off, with no errors (the pre-check tells you)."],
|
|
4729
|
+
["不用命令行工具", "Don't use the command-line tool"],
|
|
4730
|
+
["— 勾上后群里只显示成员账号、助手不能发图发文件、「不 @ 也回复」不生效", "— when checked, groups show member accounts only, the assistant can't send images or files, and \"Reply without @\" has no effect"],
|
|
4731
|
+
["保存企业微信", "Save WeCom"],
|
|
4732
|
+
["长连接 Secret", "Long-connection Secret"],
|
|
4733
|
+
["门控 无", "Gate: none"],
|
|
4734
|
+
["工具 开", "CLI tool: on"],
|
|
4735
|
+
["工具 关", "CLI tool: off"],
|
|
4736
|
+
["企业微信已保存", "WeCom saved"],
|
|
4737
|
+
["取中…", "Fetching…"],
|
|
4738
|
+
["没取到", "Nothing found"],
|
|
4739
|
+
["工具授权身份", "Tool authorization"],
|
|
4740
|
+
["通讯录授权", "Directory authorization"],
|
|
4741
|
+
["可绑定的群", "Bindable groups"],
|
|
4742
|
+
["Bot ID / Secret 已配(环境变量引用,由守护进程解析)", "Bot ID / Secret set (environment variable references, resolved by the daemon)"],
|
|
4743
|
+
["Bot ID / Secret 已配", "Bot ID / Secret set"],
|
|
4744
|
+
["缺 Secret", "Secret missing"],
|
|
4745
|
+
["缺 Bot ID", "Bot ID missing"],
|
|
4746
|
+
["缺 Bot ID 与 Secret", "Bot ID and Secret missing"],
|
|
4747
|
+
["配置里已关闭 — 群里只显示成员账号,助手不能发图发文件", "Turned off in the config — groups show member accounts only, and the assistant can't send images or files"],
|
|
4748
|
+
["已安装并授权", "Installed and authorized"],
|
|
4749
|
+
["未授权 — 用这里填的 Bot ID / Secret 给命令行工具授权一次", "Not authorized — authorize the command-line tool once with the Bot ID / Secret entered here"],
|
|
4750
|
+
["可查成员姓名(授权每 7 天一续,过期会进运维告警群)", "Member names available (the authorization renews every 7 days; expiry is reported to the ops alert group)"],
|
|
4751
|
+
["已过期 — 请机器人创建者在企业微信里点续期链接;过期期间群里只显示成员账号", "Expired — ask the bot's creator to click the renewal link in WeCom; until then groups show member accounts only"],
|
|
4752
|
+
["0 个 — 新群要先把机器人加进群并 @ 一次", "0 — add the bot to a new group and @ it once first"],
|
|
4753
|
+
["未安装 — 群里只显示成员账号、助手不能发图发文件、这里列不出群(装好后按机器人凭据授权一次即可)", "Not installed — groups show member accounts only, the assistant can't send images or files, and no groups can be listed here (after installing, authorize it once with the bot's credentials)"],
|
|
4754
|
+
["长连接只有守护进程连得上(一个机器人只许一条),连接状态看「概览 → 企业微信连接」那一行", "Only the daemon can hold the long connection (one per bot); see the \"Overview → WeCom connection\" row for its status"],
|
|
4755
|
+
["先在设置里填 appId/appSecret", "fill in appId/appSecret in Settings first"],
|
|
4756
|
+
["还没有机器人被 @ 过的群", "No group has @-mentioned the bot yet"],
|
|
4757
|
+
["bot 不在任何群 / 未配飞书", "The bot is in no group, or Feishu isn't configured"],
|
|
4557
4758
|
];
|
|
4558
4759
|
const I18N_EN_RX=[
|
|
4760
|
+
// ── 10-24 英文界面核对补齐 ──
|
|
4761
|
+
// 插件可达性探测的结果句(src/plugins.js probeMcp 的全部句式),控制台拼在插件行的悬停提示里
|
|
4762
|
+
[/^(.+):(\d+) 在听$/,"$1:$2 listening"],
|
|
4763
|
+
[/^连不上 (\S+?)\((\w+) — 那个端口上没有服务在听\)$/,"Can't reach $1 ($2 — nothing is listening on that port)"],
|
|
4764
|
+
[/^直连未通 (\S+?)\((\w+)\)$/,"Direct connection failed: $1 ($2)"],
|
|
4765
|
+
[/^探测失败 (.+)$/,"Probe failed: $1"],
|
|
4766
|
+
[/^(.+) 在,但没有可执行权限$/,"$1 exists but isn't executable"],
|
|
4767
|
+
[/^(.+) 不在 PATH 上$/,"$1 is not on PATH"],
|
|
4768
|
+
[/^找不到 (.+)$/,"Can't find $1"],
|
|
4769
|
+
[/^([\s\S]*) · 仍会照常加载 —— 本机直连探测不走代理,探不通不代表它不可用$/,(a,d)=>trText(d)+" · still loaded as usual — the local probe connects directly without the proxy, so a failed probe doesn't mean it's unusable"],
|
|
4770
|
+
// 健康 ·「可用模型」那一行:能用的个数(或「无可用模型」)+ 没登录 / 验证失败的名单
|
|
4771
|
+
[/^(.+?);(.+) 没登录或验证失败$/,(a,head,names)=>trText(head)+"; "+names.split("、").join(", ")+" not signed in or failed verification"],
|
|
4772
|
+
// 健康 · 平台凭据:上次预检失败的原因是服务端给的,能翻的照翻
|
|
4773
|
+
// 原因是预检那一行的「检查项:说明」(src/core/verify.js recordPrecheck),两半各自查词典
|
|
4774
|
+
[/^上次预检失败:([\s\S]+) — 去「设置」页改凭据后再预检$/,(a,why)=>"Last pre-check failed: "+why.replace(/^([^:]+):([\s\S]*)$/,(x,l,d)=>trText(l)+": "+trText(d))+" — fix the credentials on the Settings tab and check again"],
|
|
4775
|
+
// 群卡 · 定时任务的周期说明(服务端 src/core/cron.js describeCron / describeAt 的全部句式)
|
|
4776
|
+
[/^每 (\d+) 分钟$/,"Every $1 minutes"],
|
|
4777
|
+
[/^每天 (\d{1,2}:\d{2})$/,"Daily $1"],
|
|
4778
|
+
[/^工作日 (\d{1,2}:\d{2})$/,"Weekdays $1"],
|
|
4779
|
+
[/^周末 (\d{1,2}:\d{2})$/,"Weekends $1"],
|
|
4780
|
+
[/^每周([日一二三四五六](?:、[日一二三四五六])*) (\d{1,2}:\d{2})$/,(a,ds,t)=>"Every "+ds.split("、").map(d=>({日:"Sun",一:"Mon",二:"Tue",三:"Wed",四:"Thu",五:"Fri",六:"Sat"}[d])).join(", ")+" "+t],
|
|
4781
|
+
[/^每月 (\d+) 号 (\d{1,2}:\d{2})$/,"Monthly on day $1, $2"],
|
|
4782
|
+
[/^每年 (\d+) 月 (\d+) 日 (\d{1,2}:\d{2})$/,(a,m,d,t)=>"Yearly on "+["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][m-1]+" "+d+", "+t],
|
|
4783
|
+
[/^(\d{1,2})月(\d{1,2})日 (\d{1,2}:\d{2})$/,(a,m,d,t)=>["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][m-1]+" "+d+", "+t],
|
|
4784
|
+
[/^(\d{4})年(\d{1,2})月(\d{1,2})日 (\d{1,2}:\d{2})$/,(a,y,m,d,t)=>["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][m-1]+" "+d+", "+y+", "+t],
|
|
4785
|
+
// 企业微信卡 · 摘要行与身份连接条里的动态片段
|
|
4786
|
+
[/^门控 (.+)$/,"Gate: $1"],
|
|
4787
|
+
[/^最近回包 (\d+) 秒前$/,"Last ack $1 s ago"],
|
|
4788
|
+
[/^最近回包 (\d+) 分钟前$/,"Last ack $1 min ago"],
|
|
4789
|
+
[/^群里 @ 的是「(.+)」,与已存的不一致 —— 点「刷新身份」核对$/,"Groups @ it as \"$1\", which differs from the saved name — click \"Refresh identity\" to check"],
|
|
4790
|
+
// 企业微信卡 · 「刷新身份」的结果提示(名字 + 取自哪里)
|
|
4791
|
+
[/^(机器人名已更新为|仍是)「(.+?)」\((?:身份接口(?: · 授权用户 (.+))?|近 (\d+) 个群 · (\d+) 条 @ 里 (\d+) 条是它)\)$/,(a,verb,name,user,g,total,n)=>(verb==="仍是"?"Still \""+name+"\"":"Bot name updated to \""+name+"\"")+(g?" (from "+g+" groups: "+n+" of "+total+" @-mentions)":" (from the identity API"+(user?", authorized user "+user:"")+")")],
|
|
4792
|
+
// 企业微信预检结果里的动态句
|
|
4793
|
+
[/^授权给了另一个机器人\((.+)\)— 成员姓名照常,但助手发的图 \/ 文件和下面列出的群都属于那个机器人;请用这里的凭据重新授权$/,"Authorized for a different bot ($1) — member names still work, but images and files the assistant sends and the groups listed below belong to that bot; authorize again with the credentials here"],
|
|
4794
|
+
[/^同一个机器人\((.+)\)$/,"Same bot ($1)"],
|
|
4795
|
+
[/^查不到授权状态:([\s\S]*)$/,"Can't read the authorization status: $1"],
|
|
4796
|
+
[/^查不通:([\s\S]*)$/,"Can't reach it: $1"],
|
|
4797
|
+
[/^(\d+) 个\(机器人被 @ 过的群\)$/,"$1 (groups where the bot has been @-mentioned)"],
|
|
4798
|
+
[/^已安装但调不通:([\s\S]*) — 多半是没用机器人凭据授权,或授权已过期$/,"Installed but not responding: $1 — most likely it wasn't authorized with the bot's credentials, or the authorization expired"],
|
|
4799
|
+
[/^1 条反馈待处理$/,"1 feedback item open"],
|
|
4800
|
+
[/^(\d+) 条反馈待处理$/,"$1 feedback items open"],
|
|
4801
|
+
[/^1 个后台进程还在跑$/,"1 background process still running"],
|
|
4802
|
+
[/^(\d+) 个后台进程还在跑$/,"$1 background processes still running"],
|
|
4803
|
+
[/^(\d+) 个还在跑$/,"$1 still running"],
|
|
4804
|
+
[/^1 项健康检查没通过$/,"1 health check failing"],
|
|
4805
|
+
[/^(\d+) 项健康检查没通过$/,"$1 health checks failing"],
|
|
4806
|
+
[/^(\d+) 个能用$/,"$1 usable"],
|
|
4807
|
+
[/^(\d+) 个能用\(含 Codex\)$/,"$1 usable (Codex included)"],
|
|
4808
|
+
[/^(\d+) 个已绑定$/,"$1 bound"],
|
|
4559
4809
|
[/^命令前缀 (\S+)$/,"Command prefix $1"],
|
|
4560
4810
|
[/^管理员 (\d+) 人$/,"$1 admins"],
|
|
4561
4811
|
[/^(\d+) 失败 · (\d+) 警告$/,"$1 failed · $2 warnings"],
|
|
@@ -4623,14 +4873,14 @@ const I18N_EN_RX=[
|
|
|
4623
4873
|
[/^语义名 (\d+) 条$/,"$1 semantic names"],
|
|
4624
4874
|
[/^装上 (.+) 后这里会出样张$/,"Install $1 and samples will appear here"],
|
|
4625
4875
|
[/^风格库读取失败:(.+)$/,"Failed to load the style library: $1"],
|
|
4626
|
-
|
|
4876
|
+
// 三种及以上的组合(上面成对的穷举只到两种):逐个换成英文名,别留半句中文(「手绘温度」是宋体·黑体·手写)
|
|
4877
|
+
[/^中文((?:宋体|圆体|手写|黑体)(?:·(?:宋体|圆体|手写|黑体))+)$/,(all,k)=>"Chinese: "+k.split("·").map(x=>({宋体:"serif",圆体:"rounded",手写:"handwritten",黑体:"sans"}[x])).join("·")],
|
|
4627
4878
|
[/^圆角 (\d+(?:\.\d+)?)px$/,"Radius $1px"],
|
|
4628
4879
|
[/^这套风格里图标的画法:实心 · 描边 (\S+) · 端点 (\S+) · 尺寸档 (\d+) 个。图形从哪来是另一件事,由项目的图标系统决定$/,"How this style draws icons: filled · stroke $1 · caps $2 · $3 size steps. Where the shapes come from is a separate matter, decided by the project icon system"],
|
|
4629
4880
|
[/^这套风格里图标的画法:线性 · 描边 (\S+) · 端点 (\S+) · 尺寸档 (\d+) 个。图形从哪来是另一件事,由项目的图标系统决定$/,"How this style draws icons: outlined · stroke $1 · caps $2 · $3 size steps. Where the shapes come from is a separate matter, decided by the project icon system"],
|
|
4630
4881
|
[/^这套风格里图标的画法:实心 · 描边 (\S+) · 端点 (\S+)。图形从哪来是另一件事,由项目的图标系统决定$/,"How this style draws icons: filled · stroke $1 · caps $2. Where the shapes come from is a separate matter, decided by the project icon system"],
|
|
4631
4882
|
[/^这套风格里图标的画法:线性 · 描边 (\S+) · 端点 (\S+)。图形从哪来是另一件事,由项目的图标系统决定$/,"How this style draws icons: outlined · stroke $1 · caps $2. Where the shapes come from is a separate matter, decided by the project icon system"],
|
|
4632
4883
|
[/^图标描边 (\S+)$/,"Icon stroke $1"],
|
|
4633
|
-
[/^适合 · (.+)$/,"Suits · $1"],
|
|
4634
4884
|
[/^(\d+) 套 · 这台机器上配的 · 全部群都能用$/,"$1 styles · configured on this machine · available to all groups"],
|
|
4635
4885
|
[/^1 套$/,"1 style"],
|
|
4636
4886
|
[/^(\d+) 套$/,"$1 styles"],
|
|
@@ -4642,7 +4892,9 @@ const I18N_EN_RX=[
|
|
|
4642
4892
|
[/^行高 (\S+)$/,"Line height $1"],
|
|
4643
4893
|
[/^投影 (\w+)$/,"Shadow $1"],
|
|
4644
4894
|
[/^这个目录里已经有一份风格档\((.+)\)—— 直接取了它的设定,没有再去猜$/,"This folder already has a style profile ($1) — its settings were used directly, no guessing"],
|
|
4895
|
+
[/^读了 1 份样式表$/,"Read 1 stylesheet"],
|
|
4645
4896
|
[/^读了 (\d+) 份样式表$/,"Read $1 stylesheets"],
|
|
4897
|
+
[/^扫了 1 个样式文件$/,"Scanned 1 style file"],
|
|
4646
4898
|
[/^扫了 (\d+) 个样式文件$/,"Scanned $1 style files"],
|
|
4647
4899
|
[/^字体族 · (.+)$/,"Font families · $1"],
|
|
4648
4900
|
[/^圆角取值 · (.+)px$/,"Radius values · $1px"],
|
|
@@ -4689,10 +4941,11 @@ const I18N_EN_RX=[
|
|
|
4689
4941
|
[/^技能 (\d+) · 已启用 · MCP (\d+) · Skill (\d+)$/,"Skills $1 · Enabled · MCP $2 · Skill $3"],
|
|
4690
4942
|
[/^项目 (\d+) · 技能 (\d+) · 已启用 · MCP (\d+)$/,"Project MCP $1 · Skills $2 · Enabled · MCP $3"],
|
|
4691
4943
|
[/^项目 (\d+) · 技能 (\d+) · 已启用 · MCP (\d+) · Skill (\d+)$/,"Project MCP $1 · Skills $2 · Enabled · MCP $3 · Skill $4"],
|
|
4692
|
-
[/^([\s\S]*) · 勾选了它的群这一轮会跳过它,不加载也不重试;服务起来后自动恢复$/,"
|
|
4693
|
-
[/^([\s\S]*) · 仍会照常加载 —— 本机直连探测不走代理,探不通不代表它不可用$/,"$1 · still loaded as usual — the local direct probe does not go through the proxy, so a failed probe does not mean it is unavailable"],
|
|
4944
|
+
[/^([\s\S]*) · 勾选了它的群这一轮会跳过它,不加载也不重试;服务起来后自动恢复$/,(a,d)=>trText(d)+" · groups that ticked it skip it this turn — not loaded, not retried; it comes back on its own once the service is up"],
|
|
4694
4945
|
[/^拉列表失败:([\s\S]+) · 可手动填 频道 ID$/,"Failed to load the list: $1 · you can enter a channel ID manually"],
|
|
4695
|
-
|
|
4946
|
+
// 选群弹窗的空状态 / 失败:「原因 · 可手动填 chatId(Slack 是频道 ID)」。原来这条写成了「拉列表失败」,一次都没匹配上过
|
|
4947
|
+
[/^拉群列表失败:([\s\S]+?) · 可手动填 (chatId|频道 ID)$/,(a,why,id)=>"Couldn't load the group list: "+trText(why)+" · you can enter a "+(id==="chatId"?"chatId":"channel ID")+" manually"],
|
|
4948
|
+
[/^([\s\S]+?) · 可手动填 (chatId|频道 ID)$/,(a,why,id)=>trText(why)+" · you can enter a "+(id==="chatId"?"chatId":"channel ID")+" manually"],
|
|
4696
4949
|
[/^须在 (.+) 或 (.+) 下$/,"Must be under $1 or $2"],
|
|
4697
4950
|
[/^须在 (.+) 下$/,"Must be under $1"],
|
|
4698
4951
|
[/^(.*)项目名$/,"$1project-name"],
|
|
@@ -4712,7 +4965,7 @@ const I18N_EN_RX=[
|
|
|
4712
4965
|
[/^搜索姓名…\(共 (\d+) 人,点名即添加\)$/,"Search by name… ($1 people; click a name to add)"],
|
|
4713
4966
|
[/^已加 (.+)$/,"Added $1"],
|
|
4714
4967
|
[/^已配置 · (.+)$/,"Configured · $1"],
|
|
4715
|
-
[/^飞书登录未就绪:([\s\S]*)$/,"Feishu sign-in is not ready:
|
|
4968
|
+
[/^飞书登录未就绪:([\s\S]*)$/,(a,why)=>"Feishu sign-in is not ready: "+trText(why)],
|
|
4716
4969
|
[/^运行中 (.+)$/,"Running $1"],
|
|
4717
4970
|
[/^重连 (\d+)$/,"$1 reconnects"],
|
|
4718
4971
|
[/^重建 (\d+)$/,"$1 rebuilds"],
|
|
@@ -4793,6 +5046,9 @@ function i18nAttr(el,a){
|
|
|
4793
5046
|
}
|
|
4794
5047
|
function i18nSkip(el){const t=(el.tagName||'').toUpperCase();
|
|
4795
5048
|
return t==='SCRIPT'||t==='STYLE'||t==='TEXTAREA'||!!(el.hasAttribute&&el.hasAttribute('data-i18n-skip'));}
|
|
5049
|
+
/* 多行输入框:里面的文字是人填的,不翻;但它的 placeholder / title 是界面文字,要翻(此前整个跳过,
|
|
5050
|
+
群卡的 allowedTools 提示、企业微信的「成员显示名」示例在英文界面一直是中文) */
|
|
5051
|
+
const i18nAttrsOnly=el=>(el.tagName||'').toUpperCase()==='TEXTAREA'&&!(el.hasAttribute&&el.hasAttribute('data-i18n-skip'));
|
|
4796
5052
|
function i18nElem(el){
|
|
4797
5053
|
for(const a of I18N_ATTRS)if(el.hasAttribute&&el.hasAttribute(a))i18nAttr(el,a);
|
|
4798
5054
|
if(el.tagName==='INPUT'&&/^(button|submit|reset)$/i.test(el.type||'')&&el.hasAttribute('value'))i18nAttr(el,'value');
|
|
@@ -4802,9 +5058,9 @@ function applyI18n(root){
|
|
|
4802
5058
|
root=root||document.body; if(!root)return;
|
|
4803
5059
|
if(root.nodeType===3){const p=root.parentElement; if(!p||!p.closest('script,style,textarea,[data-i18n-skip]'))i18nText(root); return;}
|
|
4804
5060
|
if(root.nodeType!==1&&root.nodeType!==9&&root.nodeType!==11)return;
|
|
4805
|
-
if(root.nodeType===1){if(i18nSkip(root))return;i18nElem(root);}
|
|
5061
|
+
if(root.nodeType===1){if(i18nSkip(root)){if(i18nAttrsOnly(root))i18nElem(root);return;}i18nElem(root);}
|
|
4806
5062
|
const w=document.createTreeWalker(root,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT,{acceptNode(n){
|
|
4807
|
-
|
|
5063
|
+
if(n.nodeType===1&&i18nSkip(n)){if(i18nAttrsOnly(n))i18nElem(n);return NodeFilter.FILTER_REJECT;}return NodeFilter.FILTER_ACCEPT;}});
|
|
4808
5064
|
for(let n=w.nextNode();n;n=w.nextNode()){if(n.nodeType===1)i18nElem(n);else i18nText(n);}
|
|
4809
5065
|
}
|
|
4810
5066
|
/* 页面每次重渲染(SSE 心跳 / 切页 / 弹窗)都走这里补译。自触发防护:我们自己写进去的值记在
|
|
@@ -4815,7 +5071,7 @@ try{new MutationObserver((muts)=>{
|
|
|
4815
5071
|
else if(m.type==='characterData'){const n=m.target,p=n.parentElement;
|
|
4816
5072
|
if(!p||!p.closest('script,style,textarea,[data-i18n-skip]'))i18nText(n);}
|
|
4817
5073
|
else if(m.type==='attributes'){const el=m.target;
|
|
4818
|
-
if(el.nodeType===1
|
|
5074
|
+
if(el.nodeType===1&&(!i18nSkip(el)||i18nAttrsOnly(el))&&el.hasAttribute(m.attributeName))i18nAttr(el,m.attributeName);}
|
|
4819
5075
|
}catch(e){}}
|
|
4820
5076
|
}).observe(document.body,{childList:true,subtree:true,characterData:true,attributes:true,attributeFilter:I18N_ATTRS});}catch(e){}
|
|
4821
5077
|
/* 原生确认框 / 输入框的提示语也是界面文案。包一层原生实现,好过在十几个调用点各插一次
|
|
@@ -4931,7 +5187,7 @@ enhanceSelects();
|
|
|
4931
5187
|
// time) and a batch replay from /api/activity (下一步 ②). Preserves a row's expand state.
|
|
4932
5188
|
function applyTurnEvent(a){
|
|
4933
5189
|
if(!a||!a.id)return;
|
|
4934
|
-
const entry={id:a.id,ts:a.ts,t:fmtHM(a.ts),group:a.group,sender:a.sender,text:a.text,status:a.kind==='start'?'running':statusLabel(a.status,a.ms),kind:a.kind==='start'?'running':(a.status||''),sec:a.ms?(a.ms/1000).toFixed(1)+'s':'',ms:a.ms,tokens:a.tokens,preview:a.preview,reply:a.reply,tools:a.tools};
|
|
5190
|
+
const entry={id:a.id,ts:a.ts,t:fmtHM(a.ts),chatId:a.chatId,err:a.err||'',group:a.group,sender:a.sender,text:a.text,status:a.kind==='start'?'running':statusLabel(a.status,a.ms),kind:a.kind==='start'?'running':(a.status||''),sec:a.ms?(a.ms/1000).toFixed(1)+'s':'',ms:a.ms,tokens:a.tokens,preview:a.preview,reply:a.reply,tools:a.tools};
|
|
4935
5191
|
// running 行记一个**浏览器本机**时刻,给上面那道自愈闸当基准。
|
|
4936
5192
|
// ⚠ 不能拿 a.ts 当基准 —— 那是 daemon 那台机器的钟,与浏览器不是同一把(v0.11.12 同款陷阱)。
|
|
4937
5193
|
if(entry.status==='running')entry.runAt=Date.now();
|
|
@@ -4964,7 +5220,9 @@ function startLive(){
|
|
|
4964
5220
|
es.addEventListener('tick',ev=>{try{const d=JSON.parse(ev.data);if(S){if(d.live)S.live=d.live;if('conn' in d)S.conn=d.conn;}updateDaemon();updateOverviewLive();updateConnLive();}catch{}});
|
|
4965
5221
|
startAutoRefresh();
|
|
4966
5222
|
es.addEventListener('conn',ev=>{try{const c=JSON.parse(ev.data);if(S){S.conn=Object.assign({},connDict(S.conn),{[c.platform||'feishu']:c});updateConnLive();}}catch{}}); // 连接状态变化即时推(不等心跳);事件带 platform 名,落进字典对应槽
|
|
4967
|
-
es.addEventListener('turn',ev=>{try{
|
|
5223
|
+
es.addEventListener('turn',ev=>{try{const d=JSON.parse(ev.data);applyTurnEvent(d);updateOverviewLive();
|
|
5224
|
+
// 收件箱跟着轮次走:刚失败的要尽快出现,重说成功的要尽快消失 —— 不等 60 秒的例行刷新;只取这一块,不拉整份 state
|
|
5225
|
+
if(d.kind==='done')refreshInbox();}catch(e){}});
|
|
4968
5226
|
es.onerror=()=>{$('#conn').classList.remove('live');checkStaleAuth();};
|
|
4969
5227
|
}catch(e){}
|
|
4970
5228
|
}
|