agim-cli 1.0.4 → 1.0.6
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.md +68 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.d.ts +14 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -1
- package/dist/plugins/messengers/wechat/ilink-adapter.js +26 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -1
- package/dist/web/public/settings.html +460 -11
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +241 -0
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -89,6 +89,45 @@
|
|
|
89
89
|
workspaceDeleted: 'Workspace deleted',
|
|
90
90
|
workspaceIdHelp: 'Letters / digits / _ / - only',
|
|
91
91
|
workspaceDefaultLocked: 'default (locked)',
|
|
92
|
+
|
|
93
|
+
// ── messengers (extras for v1.0.5) ───────────────────────
|
|
94
|
+
wechatScan: 'Scan to log in',
|
|
95
|
+
wechatScanTitle: 'WeChat — scan to log in',
|
|
96
|
+
wechatGenerating: 'Generating QR code…',
|
|
97
|
+
wechatWaiting: 'Waiting for scan…',
|
|
98
|
+
wechatScanned: 'QR scanned — confirm on your phone…',
|
|
99
|
+
wechatConfirmed: '✓ Logged in (account {account})',
|
|
100
|
+
wechatExpired: 'QR code expired',
|
|
101
|
+
wechatRegen: 'Regenerate',
|
|
102
|
+
wechatClose: 'Close',
|
|
103
|
+
wechatFailed: 'Failed: {error}',
|
|
104
|
+
dingtalk: 'DingTalk',
|
|
105
|
+
dingtalkHint: 'Stream-mode app: Client ID + Client Secret',
|
|
106
|
+
dingtalkClientId: 'Client ID',
|
|
107
|
+
dingtalkClientSecret: 'Client Secret',
|
|
108
|
+
discord: 'Discord',
|
|
109
|
+
discordHint: 'Bot token from Discord Developer Portal',
|
|
110
|
+
discordToken: 'Bot Token',
|
|
111
|
+
discordGuilds: 'Allowed guilds (comma-separated IDs, blank = any)',
|
|
112
|
+
discordChannels: 'Allowed channels (comma-separated IDs, blank = any)',
|
|
113
|
+
configure: 'Configure',
|
|
114
|
+
reconfigure: 'Edit credentials',
|
|
115
|
+
|
|
116
|
+
// ── service control ──────────────────────────────────────
|
|
117
|
+
svcTitle: 'Service',
|
|
118
|
+
svcStateLoading: 'Checking…',
|
|
119
|
+
svcStateRunning: 'Running ({mode}, pid {pid}, up {uptime})',
|
|
120
|
+
svcStateRunningNoUp: 'Running ({mode}, pid {pid})',
|
|
121
|
+
svcStateNone: 'Not running',
|
|
122
|
+
svcStart: 'Start',
|
|
123
|
+
svcStop: 'Stop',
|
|
124
|
+
svcRestart: 'Restart',
|
|
125
|
+
svcConfirmStop: 'Stop Agim? The web console will go offline until you start it again from a terminal.',
|
|
126
|
+
svcConfirmRestart: 'Restart Agim? The web console will reconnect automatically in a few seconds.',
|
|
127
|
+
svcRestarting: 'Restarting — waiting for the service to come back…',
|
|
128
|
+
svcRestarted: '✓ Service restarted',
|
|
129
|
+
svcStopped: '✓ Service stopped (this page is now disconnected)',
|
|
130
|
+
svcFgWarning: 'Foreground service is running in another terminal — restart it there.',
|
|
92
131
|
},
|
|
93
132
|
zh: {
|
|
94
133
|
title: 'Agim — 设置',
|
|
@@ -164,6 +203,45 @@
|
|
|
164
203
|
workspaceDeleted: '工作区已删除',
|
|
165
204
|
workspaceIdHelp: '仅支持字母 / 数字 / _ / -',
|
|
166
205
|
workspaceDefaultLocked: '默认(不可改)',
|
|
206
|
+
|
|
207
|
+
// ── messengers (extras for v1.0.5) ───────────────────────
|
|
208
|
+
wechatScan: '扫码登录',
|
|
209
|
+
wechatScanTitle: '微信 — 扫码登录',
|
|
210
|
+
wechatGenerating: '生成二维码中…',
|
|
211
|
+
wechatWaiting: '等待扫描…',
|
|
212
|
+
wechatScanned: '已扫描,请在手机端确认…',
|
|
213
|
+
wechatConfirmed: '✓ 登录成功(账号 {account})',
|
|
214
|
+
wechatExpired: '二维码已过期',
|
|
215
|
+
wechatRegen: '重新生成',
|
|
216
|
+
wechatClose: '关闭',
|
|
217
|
+
wechatFailed: '失败:{error}',
|
|
218
|
+
dingtalk: '钉钉',
|
|
219
|
+
dingtalkHint: 'Stream 模式应用:Client ID + Client Secret',
|
|
220
|
+
dingtalkClientId: 'Client ID',
|
|
221
|
+
dingtalkClientSecret: 'Client Secret',
|
|
222
|
+
discord: 'Discord',
|
|
223
|
+
discordHint: '在 Discord Developer Portal 获取 Bot Token',
|
|
224
|
+
discordToken: 'Bot Token',
|
|
225
|
+
discordGuilds: '允许的服务器(逗号分隔 ID,空 = 不限)',
|
|
226
|
+
discordChannels: '允许的频道(逗号分隔 ID,空 = 不限)',
|
|
227
|
+
configure: '配置',
|
|
228
|
+
reconfigure: '修改凭据',
|
|
229
|
+
|
|
230
|
+
// ── service control ──────────────────────────────────────
|
|
231
|
+
svcTitle: '服务',
|
|
232
|
+
svcStateLoading: '检查中…',
|
|
233
|
+
svcStateRunning: '运行中({mode},pid {pid},已运行 {uptime})',
|
|
234
|
+
svcStateRunningNoUp: '运行中({mode},pid {pid})',
|
|
235
|
+
svcStateNone: '未运行',
|
|
236
|
+
svcStart: '启动',
|
|
237
|
+
svcStop: '停止',
|
|
238
|
+
svcRestart: '重启',
|
|
239
|
+
svcConfirmStop: '停止 Agim?停止后 web 控制台会离线,需要回终端用 `agim start` 再启动。',
|
|
240
|
+
svcConfirmRestart: '重启 Agim?web 控制台会在几秒后自动重连。',
|
|
241
|
+
svcRestarting: '重启中——等待服务恢复…',
|
|
242
|
+
svcRestarted: '✓ 服务已重启',
|
|
243
|
+
svcStopped: '✓ 服务已停止(页面已断开连接)',
|
|
244
|
+
svcFgWarning: '前台服务在另一个终端运行——回那个终端重启。',
|
|
167
245
|
},
|
|
168
246
|
};
|
|
169
247
|
function t(key) { return T[window.__lang][key] || T.en[key] || key; }
|
|
@@ -603,6 +681,7 @@
|
|
|
603
681
|
function render() {
|
|
604
682
|
app.innerHTML = `
|
|
605
683
|
<h1>${t('h1')}</h1>
|
|
684
|
+
${renderServiceCard()}
|
|
606
685
|
${renderAgentsCard()}
|
|
607
686
|
${renderMessengersCard()}
|
|
608
687
|
${renderAcpCard()}
|
|
@@ -610,6 +689,28 @@
|
|
|
610
689
|
${renderGeneralCard()}
|
|
611
690
|
`;
|
|
612
691
|
bindEvents();
|
|
692
|
+
// Service status loads asynchronously; the card renders with a
|
|
693
|
+
// placeholder, populated by the first /api/service/status response.
|
|
694
|
+
void loadServiceStatus();
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// ==========================================
|
|
698
|
+
// Service control card
|
|
699
|
+
// ==========================================
|
|
700
|
+
function renderServiceCard() {
|
|
701
|
+
return `
|
|
702
|
+
<div class="card">
|
|
703
|
+
<h2>${t('svcTitle')}</h2>
|
|
704
|
+
<div class="status" id="svc-state" style="margin-bottom:14px">
|
|
705
|
+
<span class="dot dot-off"></span>${t('svcStateLoading')}
|
|
706
|
+
</div>
|
|
707
|
+
<div class="actions">
|
|
708
|
+
<button type="button" class="btn btn-primary" id="svc-restart">${t('svcRestart')}</button>
|
|
709
|
+
<button type="button" class="btn" id="svc-stop">${t('svcStop')}</button>
|
|
710
|
+
<button type="button" class="btn" id="svc-start" disabled>${t('svcStart')}</button>
|
|
711
|
+
</div>
|
|
712
|
+
</div>
|
|
713
|
+
`;
|
|
613
714
|
}
|
|
614
715
|
|
|
615
716
|
// ==========================================
|
|
@@ -640,16 +741,24 @@
|
|
|
640
741
|
`;
|
|
641
742
|
}).join('');
|
|
642
743
|
|
|
744
|
+
// Default-agent picker only lists agents that are BOTH enabled and
|
|
745
|
+
// installed — defaulting to a missing binary or a disabled adapter
|
|
746
|
+
// both break routing the first time someone sends a message.
|
|
747
|
+
const eligibleDefaults = agents.filter(a => enabledAgents.includes(a) && agentStatus[a]);
|
|
748
|
+
|
|
643
749
|
return `
|
|
644
750
|
<div class="card">
|
|
645
751
|
<h2>${t('agents')} <span class="badge">${agents.length}</span></h2>
|
|
646
752
|
${rows}
|
|
647
753
|
<hr class="divider">
|
|
648
754
|
<label>${t('defaultAgent')}</label>
|
|
649
|
-
<select id="defaultAgent">
|
|
650
|
-
${
|
|
651
|
-
`<option value="${esc(
|
|
652
|
-
|
|
755
|
+
<select id="defaultAgent" ${eligibleDefaults.length === 0 ? 'disabled' : ''}>
|
|
756
|
+
${eligibleDefaults.length === 0
|
|
757
|
+
? `<option value="">— ${esc(t('agents'))} —</option>`
|
|
758
|
+
: eligibleDefaults.map(a =>
|
|
759
|
+
`<option value="${esc(a)}" ${a === defaultAgent ? 'selected' : ''}>${esc(a)}</option>`
|
|
760
|
+
).join('')
|
|
761
|
+
}
|
|
653
762
|
</select>
|
|
654
763
|
<div class="actions">
|
|
655
764
|
<button type="button" class="btn btn-primary" id="saveAgents">${t('saveAgents')}</button>
|
|
@@ -665,10 +774,14 @@
|
|
|
665
774
|
const messengers = config.messengers || [];
|
|
666
775
|
const tg = config.telegram || {};
|
|
667
776
|
const fs = config.feishu || {};
|
|
777
|
+
const dt = config.dingtalk || {};
|
|
778
|
+
const dc = config.discord || {};
|
|
668
779
|
|
|
669
780
|
const wechatEnabled = messengers.includes('wechat-ilink');
|
|
670
781
|
const telegramEnabled = messengers.includes('telegram');
|
|
671
782
|
const feishuEnabled = messengers.includes('feishu');
|
|
783
|
+
const dingtalkEnabled = messengers.includes('dingtalk');
|
|
784
|
+
const discordEnabled = messengers.includes('discord');
|
|
672
785
|
|
|
673
786
|
return `
|
|
674
787
|
<div class="card">
|
|
@@ -685,7 +798,11 @@
|
|
|
685
798
|
</div>
|
|
686
799
|
<div class="toggle ${wechatEnabled ? 'active' : ''}" data-toggle-messenger="wechat-ilink"></div>
|
|
687
800
|
</div>
|
|
688
|
-
${wechatEnabled ?
|
|
801
|
+
${wechatEnabled ? `
|
|
802
|
+
<div class="actions">
|
|
803
|
+
<button type="button" class="btn" id="wechatScanBtn">${t('wechatScan')}</button>
|
|
804
|
+
</div>
|
|
805
|
+
` : ''}
|
|
689
806
|
</div>
|
|
690
807
|
|
|
691
808
|
<!-- Telegram -->
|
|
@@ -714,7 +831,7 @@
|
|
|
714
831
|
</div>
|
|
715
832
|
|
|
716
833
|
<!-- Feishu -->
|
|
717
|
-
<div>
|
|
834
|
+
<div style="margin-bottom:16px">
|
|
718
835
|
<div class="agent-row">
|
|
719
836
|
<div class="left">
|
|
720
837
|
<div>
|
|
@@ -738,6 +855,66 @@
|
|
|
738
855
|
` : ''}
|
|
739
856
|
</div>
|
|
740
857
|
|
|
858
|
+
<!-- DingTalk -->
|
|
859
|
+
<div style="margin-bottom:16px">
|
|
860
|
+
<div class="agent-row">
|
|
861
|
+
<div class="left">
|
|
862
|
+
<div>
|
|
863
|
+
<div class="name">${t('dingtalk')}</div>
|
|
864
|
+
<div class="hint">${t('dingtalkHint')}</div>
|
|
865
|
+
</div>
|
|
866
|
+
</div>
|
|
867
|
+
<div class="toggle ${dingtalkEnabled ? 'active' : ''}" data-toggle-messenger="dingtalk"></div>
|
|
868
|
+
</div>
|
|
869
|
+
${dingtalkEnabled ? `
|
|
870
|
+
<div class="row">
|
|
871
|
+
<div>
|
|
872
|
+
<label>${t('dingtalkClientId')}</label>
|
|
873
|
+
<input type="text" id="dtClientId" value="${esc(dt.clientId || '')}" placeholder="dingxxxxxx">
|
|
874
|
+
</div>
|
|
875
|
+
<div>
|
|
876
|
+
<label>${t('dingtalkClientSecret')}</label>
|
|
877
|
+
<input type="password" id="dtClientSecret" value="${esc(dt.clientSecret || '')}" placeholder="••••••••">
|
|
878
|
+
</div>
|
|
879
|
+
</div>
|
|
880
|
+
` : ''}
|
|
881
|
+
</div>
|
|
882
|
+
|
|
883
|
+
<!-- Discord -->
|
|
884
|
+
<div>
|
|
885
|
+
<div class="agent-row">
|
|
886
|
+
<div class="left">
|
|
887
|
+
<div>
|
|
888
|
+
<div class="name">${t('discord')}</div>
|
|
889
|
+
<div class="hint">${t('discordHint')}</div>
|
|
890
|
+
</div>
|
|
891
|
+
</div>
|
|
892
|
+
<div class="toggle ${discordEnabled ? 'active' : ''}" data-toggle-messenger="discord"></div>
|
|
893
|
+
</div>
|
|
894
|
+
${discordEnabled ? `
|
|
895
|
+
<div class="row">
|
|
896
|
+
<div>
|
|
897
|
+
<label>${t('discordToken')}</label>
|
|
898
|
+
<input type="password" id="dcToken" value="${esc(dc.botToken || '')}" placeholder="••••••••">
|
|
899
|
+
</div>
|
|
900
|
+
<div>
|
|
901
|
+
<label>${t('channelId')}</label>
|
|
902
|
+
<input type="text" id="dcChannel" value="${esc(dc.channelId || '')}" placeholder="default">
|
|
903
|
+
</div>
|
|
904
|
+
</div>
|
|
905
|
+
<div class="row">
|
|
906
|
+
<div>
|
|
907
|
+
<label>${t('discordGuilds')}</label>
|
|
908
|
+
<input type="text" id="dcGuilds" value="${esc((dc.allowedGuilds || []).join(', '))}" placeholder="123, 456">
|
|
909
|
+
</div>
|
|
910
|
+
<div>
|
|
911
|
+
<label>${t('discordChannels')}</label>
|
|
912
|
+
<input type="text" id="dcChannels" value="${esc((dc.allowedChannels || []).join(', '))}" placeholder="789, 101112">
|
|
913
|
+
</div>
|
|
914
|
+
</div>
|
|
915
|
+
` : ''}
|
|
916
|
+
</div>
|
|
917
|
+
|
|
741
918
|
<div class="actions">
|
|
742
919
|
<button type="button" class="btn btn-primary" id="saveMessengers">${t('saveMessengers')}</button>
|
|
743
920
|
</div>
|
|
@@ -1037,19 +1214,109 @@
|
|
|
1037
1214
|
// the Reveal buttons re-fetch with ?reveal=1 on demand.
|
|
1038
1215
|
void loadEnvSection();
|
|
1039
1216
|
|
|
1040
|
-
//
|
|
1217
|
+
// Service control card
|
|
1218
|
+
document.getElementById('svc-restart')?.addEventListener('click', () => svcAction('restart'));
|
|
1219
|
+
document.getElementById('svc-stop')?.addEventListener('click', () => svcAction('stop'));
|
|
1220
|
+
document.getElementById('svc-start')?.addEventListener('click', () => svcAction('start'));
|
|
1221
|
+
|
|
1222
|
+
// Agent toggles — flip config.agents in memory + auto-manage
|
|
1223
|
+
// defaultAgent (promote / demote as needed), then re-render so the
|
|
1224
|
+
// default-agent dropdown reflects the new eligible set.
|
|
1041
1225
|
document.querySelectorAll('[data-toggle-agent]').forEach(el => {
|
|
1042
|
-
el.addEventListener('click', () =>
|
|
1226
|
+
el.addEventListener('click', () => {
|
|
1227
|
+
const id = el.getAttribute('data-toggle-agent');
|
|
1228
|
+
const set = new Set(config.agents || []);
|
|
1229
|
+
if (set.has(id)) {
|
|
1230
|
+
set.delete(id);
|
|
1231
|
+
// If we just removed the default, promote the next still-enabled
|
|
1232
|
+
// agent (or clear). saveConfig() will sync to disk on Save.
|
|
1233
|
+
if (config.defaultAgent === id) {
|
|
1234
|
+
config.defaultAgent = Array.from(set)[0] || '';
|
|
1235
|
+
}
|
|
1236
|
+
} else {
|
|
1237
|
+
set.add(id);
|
|
1238
|
+
// First enabled agent inherits default when nothing was set.
|
|
1239
|
+
if (!config.defaultAgent) config.defaultAgent = id;
|
|
1240
|
+
}
|
|
1241
|
+
config.agents = Array.from(set);
|
|
1242
|
+
render();
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
// Default-agent dropdown — keep in-memory config in sync so the
|
|
1247
|
+
// Save button persists what the user just picked.
|
|
1248
|
+
document.getElementById('defaultAgent')?.addEventListener('change', (e) => {
|
|
1249
|
+
config.defaultAgent = e.target.value;
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
// Save Agents button — pushes the in-memory agents[] + defaultAgent
|
|
1253
|
+
// to /api/config PUT. Mirrors the saveMessengers pattern.
|
|
1254
|
+
document.getElementById('saveAgents')?.addEventListener('click', async () => {
|
|
1255
|
+
await saveConfig();
|
|
1043
1256
|
});
|
|
1044
1257
|
|
|
1045
|
-
// Messenger toggles
|
|
1258
|
+
// Messenger toggles — flip in-memory config.messengers, then re-render.
|
|
1046
1259
|
document.querySelectorAll('[data-toggle-messenger]').forEach(el => {
|
|
1047
1260
|
el.addEventListener('click', () => {
|
|
1048
|
-
el.
|
|
1049
|
-
|
|
1261
|
+
const id = el.getAttribute('data-toggle-messenger');
|
|
1262
|
+
const set = new Set(config.messengers || []);
|
|
1263
|
+
if (set.has(id)) set.delete(id);
|
|
1264
|
+
else set.add(id);
|
|
1265
|
+
config.messengers = Array.from(set);
|
|
1266
|
+
render();
|
|
1050
1267
|
});
|
|
1051
1268
|
});
|
|
1052
1269
|
|
|
1270
|
+
// Save messengers — pull credentials from any visible fields, then PUT.
|
|
1271
|
+
document.getElementById('saveMessengers')?.addEventListener('click', async () => {
|
|
1272
|
+
const get = (id) => document.getElementById(id)?.value?.trim() ?? '';
|
|
1273
|
+
const messengers = (config.messengers || []);
|
|
1274
|
+
|
|
1275
|
+
if (messengers.includes('telegram')) {
|
|
1276
|
+
const botToken = get('tgToken');
|
|
1277
|
+
const channelId = get('tgChannel') || 'default';
|
|
1278
|
+
if (botToken) config.telegram = { ...(config.telegram || {}), botToken, channelId };
|
|
1279
|
+
else if (config.telegram?.channelId !== channelId) config.telegram = { ...(config.telegram || {}), channelId };
|
|
1280
|
+
}
|
|
1281
|
+
if (messengers.includes('feishu')) {
|
|
1282
|
+
const appId = get('fsAppId');
|
|
1283
|
+
const appSecret = get('fsAppSecret');
|
|
1284
|
+
config.feishu = {
|
|
1285
|
+
...(config.feishu || {}),
|
|
1286
|
+
...(appId ? { appId } : {}),
|
|
1287
|
+
...(appSecret ? { appSecret } : {}),
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
if (messengers.includes('dingtalk')) {
|
|
1291
|
+
const clientId = get('dtClientId');
|
|
1292
|
+
const clientSecret = get('dtClientSecret');
|
|
1293
|
+
config.dingtalk = {
|
|
1294
|
+
...(config.dingtalk || {}),
|
|
1295
|
+
...(clientId ? { clientId } : {}),
|
|
1296
|
+
...(clientSecret ? { clientSecret } : {}),
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
if (messengers.includes('discord')) {
|
|
1300
|
+
const botToken = get('dcToken');
|
|
1301
|
+
const channelId = get('dcChannel') || 'default';
|
|
1302
|
+
const guildsRaw = get('dcGuilds');
|
|
1303
|
+
const channelsRaw = get('dcChannels');
|
|
1304
|
+
config.discord = {
|
|
1305
|
+
...(config.discord || {}),
|
|
1306
|
+
...(botToken ? { botToken } : {}),
|
|
1307
|
+
channelId,
|
|
1308
|
+
allowedGuilds: guildsRaw ? guildsRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
|
1309
|
+
allowedChannels: channelsRaw ? channelsRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
await saveConfig();
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
// WeChat — open QR modal on click.
|
|
1316
|
+
document.getElementById('wechatScanBtn')?.addEventListener('click', () => {
|
|
1317
|
+
openWechatQrModal();
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1053
1320
|
// ACP toggles
|
|
1054
1321
|
document.querySelectorAll('[data-toggle-acp]').forEach(el => {
|
|
1055
1322
|
el.addEventListener('click', () => el.classList.toggle('active'));
|
|
@@ -1302,6 +1569,186 @@
|
|
|
1302
1569
|
render();
|
|
1303
1570
|
}
|
|
1304
1571
|
|
|
1572
|
+
// ==========================================
|
|
1573
|
+
// WeChat QR-login modal
|
|
1574
|
+
// ==========================================
|
|
1575
|
+
let wechatPollTimer = null;
|
|
1576
|
+
function closeWechatQrModal() {
|
|
1577
|
+
if (wechatPollTimer) { clearTimeout(wechatPollTimer); wechatPollTimer = null; }
|
|
1578
|
+
const m = document.getElementById('wechat-modal');
|
|
1579
|
+
if (m) m.remove();
|
|
1580
|
+
}
|
|
1581
|
+
async function openWechatQrModal() {
|
|
1582
|
+
// Tear down any prior modal first.
|
|
1583
|
+
closeWechatQrModal();
|
|
1584
|
+
const overlay = document.createElement('div');
|
|
1585
|
+
overlay.id = 'wechat-modal';
|
|
1586
|
+
overlay.style.cssText = 'position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.55)';
|
|
1587
|
+
overlay.innerHTML = `
|
|
1588
|
+
<div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:24px 28px;max-width:380px;width:90%;text-align:center">
|
|
1589
|
+
<div style="font-weight:600;font-size:15px;margin-bottom:14px">${esc(t('wechatScanTitle'))}</div>
|
|
1590
|
+
<div id="wechat-qr-wrap" style="min-height:240px;display:flex;align-items:center;justify-content:center;background:var(--surface2);border-radius:8px;margin-bottom:12px">
|
|
1591
|
+
<div style="color:var(--text-dim);font-size:13px">${esc(t('wechatGenerating'))}</div>
|
|
1592
|
+
</div>
|
|
1593
|
+
<div id="wechat-qr-status" style="font-size:13px;color:var(--text-dim);margin-bottom:14px;min-height:18px"></div>
|
|
1594
|
+
<div style="display:flex;gap:8px;justify-content:center">
|
|
1595
|
+
<button type="button" class="btn" id="wechat-regen">${esc(t('wechatRegen'))}</button>
|
|
1596
|
+
<button type="button" class="btn" id="wechat-close">${esc(t('wechatClose'))}</button>
|
|
1597
|
+
</div>
|
|
1598
|
+
</div>
|
|
1599
|
+
`;
|
|
1600
|
+
document.body.appendChild(overlay);
|
|
1601
|
+
document.getElementById('wechat-close')?.addEventListener('click', closeWechatQrModal);
|
|
1602
|
+
document.getElementById('wechat-regen')?.addEventListener('click', () => { void startWechatQr(); });
|
|
1603
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeWechatQrModal(); });
|
|
1604
|
+
await startWechatQr();
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
async function startWechatQr() {
|
|
1608
|
+
if (wechatPollTimer) { clearTimeout(wechatPollTimer); wechatPollTimer = null; }
|
|
1609
|
+
const wrap = document.getElementById('wechat-qr-wrap');
|
|
1610
|
+
const statusEl = document.getElementById('wechat-qr-status');
|
|
1611
|
+
if (!wrap || !statusEl) return;
|
|
1612
|
+
wrap.innerHTML = `<div style="color:var(--text-dim);font-size:13px">${esc(t('wechatGenerating'))}</div>`;
|
|
1613
|
+
statusEl.textContent = '';
|
|
1614
|
+
let qrToken;
|
|
1615
|
+
try {
|
|
1616
|
+
const res = await authFetch('/api/messengers/wechat/qr-start', { method: 'POST' });
|
|
1617
|
+
if (!res.ok) {
|
|
1618
|
+
const j = await res.json().catch(() => ({}));
|
|
1619
|
+
throw new Error(j.error || res.statusText);
|
|
1620
|
+
}
|
|
1621
|
+
const data = await res.json();
|
|
1622
|
+
qrToken = data.qrToken;
|
|
1623
|
+
// qrUrl from iLink may be a data: URL (base64 png) or an https URL.
|
|
1624
|
+
// Either way, <img src=...> handles it directly.
|
|
1625
|
+
wrap.innerHTML = `<img src="${esc(data.qrUrl)}" alt="QR code" style="width:240px;height:240px;border-radius:6px;background:#fff;padding:8px">`;
|
|
1626
|
+
statusEl.textContent = t('wechatWaiting');
|
|
1627
|
+
} catch (err) {
|
|
1628
|
+
wrap.innerHTML = `<div style="color:var(--red);font-size:13px">${esc(t('wechatFailed').replace('{error}', err && err.message ? err.message : err))}</div>`;
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
// Begin polling.
|
|
1632
|
+
const poll = async () => {
|
|
1633
|
+
try {
|
|
1634
|
+
const res = await authFetch('/api/messengers/wechat/qr-status?token=' + encodeURIComponent(qrToken));
|
|
1635
|
+
if (!res.ok) {
|
|
1636
|
+
const j = await res.json().catch(() => ({}));
|
|
1637
|
+
throw new Error(j.error || res.statusText);
|
|
1638
|
+
}
|
|
1639
|
+
const data = await res.json();
|
|
1640
|
+
if (data.status === 'wait') {
|
|
1641
|
+
statusEl.textContent = t('wechatWaiting');
|
|
1642
|
+
wechatPollTimer = setTimeout(poll, 1500);
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (data.status === 'scaned') {
|
|
1646
|
+
statusEl.textContent = t('wechatScanned');
|
|
1647
|
+
wechatPollTimer = setTimeout(poll, 1000);
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
if (data.status === 'confirmed') {
|
|
1651
|
+
const account = (data.credentials && data.credentials.accountId) || '';
|
|
1652
|
+
statusEl.innerHTML = '<span style="color:var(--green)">' + esc(t('wechatConfirmed').replace('{account}', account)) + '</span>';
|
|
1653
|
+
// Refresh the underlying config (server added 'wechat-ilink' to messengers).
|
|
1654
|
+
toast(t('savedMsg'), 'success');
|
|
1655
|
+
setTimeout(() => { closeWechatQrModal(); void init(); }, 1500);
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
if (data.status === 'expired') {
|
|
1659
|
+
statusEl.innerHTML = '<span style="color:var(--red)">' + esc(t('wechatExpired')) + '</span>';
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
// Unknown status — keep polling once, then stop.
|
|
1663
|
+
wechatPollTimer = setTimeout(poll, 1500);
|
|
1664
|
+
} catch (err) {
|
|
1665
|
+
statusEl.innerHTML = '<span style="color:var(--red)">' + esc(t('wechatFailed').replace('{error}', err && err.message ? err.message : err)) + '</span>';
|
|
1666
|
+
}
|
|
1667
|
+
};
|
|
1668
|
+
wechatPollTimer = setTimeout(poll, 1000);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// ==========================================
|
|
1672
|
+
// Service-control card (status + start/stop/restart)
|
|
1673
|
+
// ==========================================
|
|
1674
|
+
let svcPollTimer = null;
|
|
1675
|
+
async function loadServiceStatus() {
|
|
1676
|
+
const stateEl = document.getElementById('svc-state');
|
|
1677
|
+
const startBtn = document.getElementById('svc-start');
|
|
1678
|
+
const stopBtn = document.getElementById('svc-stop');
|
|
1679
|
+
const restartBtn = document.getElementById('svc-restart');
|
|
1680
|
+
if (!stateEl) return;
|
|
1681
|
+
try {
|
|
1682
|
+
const res = await authFetch('/api/service/status');
|
|
1683
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
1684
|
+
const d = await res.json();
|
|
1685
|
+
if (d.mode === 'none') {
|
|
1686
|
+
stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('svcStateNone'));
|
|
1687
|
+
} else {
|
|
1688
|
+
const tpl = d.uptime ? t('svcStateRunning') : t('svcStateRunningNoUp');
|
|
1689
|
+
const label = tpl
|
|
1690
|
+
.replace('{mode}', d.mode || '?')
|
|
1691
|
+
.replace('{pid}', d.pid != null ? d.pid : '?')
|
|
1692
|
+
.replace('{uptime}', d.uptime || '');
|
|
1693
|
+
stateEl.innerHTML = '<span class="dot dot-on"></span>' + esc(label);
|
|
1694
|
+
}
|
|
1695
|
+
if (startBtn) startBtn.disabled = d.mode !== 'none';
|
|
1696
|
+
if (stopBtn) stopBtn.disabled = d.mode === 'none';
|
|
1697
|
+
if (restartBtn) restartBtn.disabled = d.mode === 'none' || d.mode === 'foreground';
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
stateEl.textContent = (t('error') + ': ' + (err && err.message ? err.message : err));
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
async function svcAction(action) {
|
|
1703
|
+
const confirmKey = action === 'stop' ? 'svcConfirmStop' : action === 'restart' ? 'svcConfirmRestart' : null;
|
|
1704
|
+
if (confirmKey && !confirm(t(confirmKey))) return;
|
|
1705
|
+
const stateEl = document.getElementById('svc-state');
|
|
1706
|
+
try {
|
|
1707
|
+
const res = await authFetch('/api/service/' + action, { method: 'POST' });
|
|
1708
|
+
if (action === 'restart') {
|
|
1709
|
+
// The HTTP response may not arrive before the parent process
|
|
1710
|
+
// SIGTERMs itself. Either way, drop into a "wait for reconnect" loop.
|
|
1711
|
+
if (stateEl) stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('svcRestarting'));
|
|
1712
|
+
await waitForServiceBack(stateEl);
|
|
1713
|
+
if (stateEl) {
|
|
1714
|
+
// loadServiceStatus refreshes the badge to live state.
|
|
1715
|
+
await loadServiceStatus();
|
|
1716
|
+
toast(t('svcRestarted'), 'success');
|
|
1717
|
+
}
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
if (action === 'stop') {
|
|
1721
|
+
if (stateEl) stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('svcStopped'));
|
|
1722
|
+
// Don't poll — the server is gone.
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (!res.ok) {
|
|
1726
|
+
const j = await res.json().catch(() => ({}));
|
|
1727
|
+
throw new Error(j.error || res.statusText);
|
|
1728
|
+
}
|
|
1729
|
+
await loadServiceStatus();
|
|
1730
|
+
} catch (err) {
|
|
1731
|
+
toast(t('error') + ': ' + (err && err.message ? err.message : err), 'error');
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
async function waitForServiceBack(stateEl) {
|
|
1735
|
+
// The new daemon takes ~2-4s to come back. Poll /api/service/status
|
|
1736
|
+
// every 700ms for up to 30s. Each successful response means the new
|
|
1737
|
+
// process has bound the port.
|
|
1738
|
+
const start = Date.now();
|
|
1739
|
+
for (;;) {
|
|
1740
|
+
if (Date.now() - start > 30000) {
|
|
1741
|
+
if (stateEl) stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('error'));
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
try {
|
|
1745
|
+
const r = await fetch('/api/service/status', { credentials: 'same-origin' });
|
|
1746
|
+
if (r.ok) return;
|
|
1747
|
+
} catch { /* not back yet */ }
|
|
1748
|
+
await new Promise(r => setTimeout(r, 700));
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1305
1752
|
// ==========================================
|
|
1306
1753
|
// API helpers
|
|
1307
1754
|
// ==========================================
|
|
@@ -1313,6 +1760,8 @@
|
|
|
1313
1760
|
defaultAgent: config.defaultAgent,
|
|
1314
1761
|
telegram: config.telegram,
|
|
1315
1762
|
feishu: config.feishu,
|
|
1763
|
+
dingtalk: config.dingtalk,
|
|
1764
|
+
discord: config.discord,
|
|
1316
1765
|
acpAgents: config.acpAgents,
|
|
1317
1766
|
webPort: config.webPort,
|
|
1318
1767
|
};
|
package/dist/web/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAqDA,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAqDA,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAknB/C"}
|