acp-ts 1.2.4 → 1.2.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/dist/agentcp.d.ts +13 -0
- package/dist/agentcp.js +39 -11
- package/dist/agentmd.d.ts +1 -1
- package/dist/agentmd.js +2 -2
- package/dist/datamanager.d.ts +2 -0
- package/dist/datamanager.js +7 -3
- package/dist/group/message_store.js +2 -0
- package/dist/group/operations.js +8 -6
- package/dist/server.js +401 -108
- package/package.json +1 -1
package/dist/agentcp.d.ts
CHANGED
|
@@ -177,6 +177,19 @@ declare class AgentCP implements IAgentCP {
|
|
|
177
177
|
* @param limit 每次拉取数量上限,0 表示使用服务端默认值
|
|
178
178
|
*/
|
|
179
179
|
pullAndStoreGroupMessages(groupId: string, afterMsgId?: number, limit?: number): Promise<GroupMessage[]>;
|
|
180
|
+
/**
|
|
181
|
+
* 将群组加入在线列表(不发送网络请求)。
|
|
182
|
+
*/
|
|
183
|
+
addOnlineGroup(groupId: string): void;
|
|
184
|
+
/**
|
|
185
|
+
* 确保群组心跳定时器已启动(公开方法)。
|
|
186
|
+
*/
|
|
187
|
+
ensureGroupHeartbeat(): void;
|
|
188
|
+
/**
|
|
189
|
+
* 向 group.ap 发送一次 register_online,告知当前客户端在线。
|
|
190
|
+
* 不带群组 ID,只需在启动或重连时调用一次。
|
|
191
|
+
*/
|
|
192
|
+
groupRegisterOnline(): Promise<void>;
|
|
180
193
|
/**
|
|
181
194
|
* 加入群组会话(完整生命周期):
|
|
182
195
|
* 1. register_online → 告知 group.ap 在线
|
package/dist/agentcp.js
CHANGED
|
@@ -77,7 +77,7 @@ class AgentCP {
|
|
|
77
77
|
}
|
|
78
78
|
const baseUrl = `https://acp3.${apiUrl}`;
|
|
79
79
|
this.seedPassword = seedPassword;
|
|
80
|
-
this._basePath = basePath ||
|
|
80
|
+
this._basePath = basePath || datamanager_1.DEFAULT_ACP_DIR;
|
|
81
81
|
this.apUrl = `${baseUrl}/api/accesspoint`;
|
|
82
82
|
this.msgUrl = `${baseUrl}/api/message`;
|
|
83
83
|
this._persistGroupMessages = (_a = options === null || options === void 0 ? void 0 : options.persistGroupMessages) !== null && _a !== void 0 ? _a : true;
|
|
@@ -460,8 +460,9 @@ class AgentCP {
|
|
|
460
460
|
* 返回排序后的消息列表,供上层使用(如推送给浏览器)。
|
|
461
461
|
*/
|
|
462
462
|
async processAndAckBatch(groupId, batch) {
|
|
463
|
-
const
|
|
464
|
-
|
|
463
|
+
const batchMessages = batch.messages || [];
|
|
464
|
+
const sorted = [...batchMessages].sort((a, b) => a.msg_id - b.msg_id);
|
|
465
|
+
utils_1.logger.log(`[AgentCP] processAndAckBatch: group=${groupId} batchCount=${batchMessages.length} sortedCount=${sorted.length} msgIds=[${sorted.map(m => m.msg_id).join(',')}]`);
|
|
465
466
|
const storeExists = !!this.groupMessageStore;
|
|
466
467
|
const storeGroupExists = storeExists ? !!this.groupMessageStore.getGroup(groupId) : false;
|
|
467
468
|
utils_1.logger.log(`[AgentCP] processAndAckBatch: storeExists=${storeExists} storeGroupExists=${storeGroupExists} lastMsgId=${this.getGroupLastMsgId(groupId)}`);
|
|
@@ -681,10 +682,12 @@ class AgentCP {
|
|
|
681
682
|
});
|
|
682
683
|
await this.addGroupMessagesToStore(groupId, msgs);
|
|
683
684
|
// ACK 这批消息中的最后一条
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
685
|
+
if (msgs.length > 0) {
|
|
686
|
+
const lastMsgId = msgs[msgs.length - 1].msg_id;
|
|
687
|
+
await this.groupOps.ackMessages(this._groupTargetAid, groupId, lastMsgId);
|
|
688
|
+
// 更新 after 用于下一轮拉取
|
|
689
|
+
after = lastMsgId;
|
|
690
|
+
}
|
|
688
691
|
if (!pulled.has_more) {
|
|
689
692
|
break;
|
|
690
693
|
}
|
|
@@ -698,6 +701,29 @@ class AgentCP {
|
|
|
698
701
|
// ============================================================
|
|
699
702
|
// Group Session Lifecycle (register_online / pull / heartbeat / unregister)
|
|
700
703
|
// ============================================================
|
|
704
|
+
/**
|
|
705
|
+
* 将群组加入在线列表(不发送网络请求)。
|
|
706
|
+
*/
|
|
707
|
+
addOnlineGroup(groupId) {
|
|
708
|
+
this._onlineGroups.add(groupId);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* 确保群组心跳定时器已启动(公开方法)。
|
|
712
|
+
*/
|
|
713
|
+
ensureGroupHeartbeat() {
|
|
714
|
+
this._ensureHeartbeat();
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* 向 group.ap 发送一次 register_online,告知当前客户端在线。
|
|
718
|
+
* 不带群组 ID,只需在启动或重连时调用一次。
|
|
719
|
+
*/
|
|
720
|
+
async groupRegisterOnline() {
|
|
721
|
+
if (!this.groupOps || !this._groupTargetAid) {
|
|
722
|
+
throw new Error('群组客户端未初始化,请先调用 initGroupClient');
|
|
723
|
+
}
|
|
724
|
+
await this.groupOps.registerOnline(this._groupTargetAid);
|
|
725
|
+
utils_1.logger.log(`[Group] registerOnline: 已通知 group.ap 在线`);
|
|
726
|
+
}
|
|
701
727
|
/**
|
|
702
728
|
* 加入群组会话(完整生命周期):
|
|
703
729
|
* 1. register_online → 告知 group.ap 在线
|
|
@@ -708,11 +734,13 @@ class AgentCP {
|
|
|
708
734
|
if (!this.groupOps || !this._groupTargetAid) {
|
|
709
735
|
throw new Error('群组客户端未初始化,请先调用 initGroupClient');
|
|
710
736
|
}
|
|
711
|
-
//
|
|
712
|
-
|
|
737
|
+
// 如果还没有在线群组,说明是首次加入,需要先 registerOnline
|
|
738
|
+
if (this._onlineGroups.size === 0) {
|
|
739
|
+
await this.groupOps.registerOnline(this._groupTargetAid);
|
|
740
|
+
}
|
|
713
741
|
this._onlineGroups.add(groupId);
|
|
714
742
|
utils_1.logger.log(`[Group] joinGroupSession: group=${groupId}`);
|
|
715
|
-
//
|
|
743
|
+
// 冷启动同步 — 拉取历史消息对齐,再进入批推送接收
|
|
716
744
|
try {
|
|
717
745
|
const lastMsgId = this.getGroupLastMsgId(groupId);
|
|
718
746
|
await this.pullAndStoreGroupMessages(groupId, lastMsgId, 50);
|
|
@@ -720,7 +748,7 @@ class AgentCP {
|
|
|
720
748
|
catch (e) {
|
|
721
749
|
utils_1.logger.warn(`[Group] cold-start sync failed: group=${groupId}`, e.message || e);
|
|
722
750
|
}
|
|
723
|
-
//
|
|
751
|
+
// 启动心跳定时器(首次加入群组时启动)
|
|
724
752
|
this._ensureHeartbeat();
|
|
725
753
|
}
|
|
726
754
|
/**
|
package/dist/agentmd.d.ts
CHANGED
package/dist/agentmd.js
CHANGED
|
@@ -7,10 +7,10 @@ exports.extractDisplayName = extractDisplayName;
|
|
|
7
7
|
exports.generateAgentMd = generateAgentMd;
|
|
8
8
|
/**
|
|
9
9
|
* 从 AID 中提取显示名称
|
|
10
|
-
* 例如: "alice.
|
|
10
|
+
* 例如: "alice.agentcp.io" -> "alice"
|
|
11
11
|
*/
|
|
12
12
|
function extractDisplayName(aid) {
|
|
13
|
-
const suffixes = ['.agentcp.io', '.
|
|
13
|
+
const suffixes = ['.agentcp.io', '.agentid.pub'];
|
|
14
14
|
for (const suffix of suffixes) {
|
|
15
15
|
if (aid.endsWith(suffix)) {
|
|
16
16
|
return aid.slice(0, -suffix.length);
|
package/dist/datamanager.d.ts
CHANGED
package/dist/datamanager.js
CHANGED
|
@@ -33,9 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.CertAndKeyStore = void 0;
|
|
36
|
+
exports.CertAndKeyStore = exports.DEFAULT_ACP_DIR = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
// 默认数据目录:用户主目录下的 acp 文件夹
|
|
41
|
+
const DEFAULT_ACP_DIR = path.join(os.homedir(), 'acp');
|
|
42
|
+
exports.DEFAULT_ACP_DIR = DEFAULT_ACP_DIR;
|
|
39
43
|
// 本地 logger(避免与 utils.ts 循环依赖)
|
|
40
44
|
function _ts() {
|
|
41
45
|
const n = new Date();
|
|
@@ -96,7 +100,7 @@ class NodeStorage {
|
|
|
96
100
|
return (_a = this.cache[key]) !== null && _a !== void 0 ? _a : null;
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
|
-
NodeStorage.dataDir = path.join(
|
|
103
|
+
NodeStorage.dataDir = path.join(DEFAULT_ACP_DIR, '.acp-data');
|
|
100
104
|
NodeStorage.dataFile = path.join(NodeStorage.dataDir, 'storage.json');
|
|
101
105
|
NodeStorage.cache = {};
|
|
102
106
|
NodeStorage.initialized = false;
|
|
@@ -265,4 +269,4 @@ class CertAndKeyStore {
|
|
|
265
269
|
}
|
|
266
270
|
exports.CertAndKeyStore = CertAndKeyStore;
|
|
267
271
|
CertAndKeyStore.aidKey = 'currentAidKey';
|
|
268
|
-
CertAndKeyStore.basePath =
|
|
272
|
+
CertAndKeyStore.basePath = DEFAULT_ACP_DIR;
|
|
@@ -180,6 +180,8 @@ class GroupMessageStore {
|
|
|
180
180
|
utils_1.logger.warn(`[GroupMessageStore] addMessages: group=${groupId} NOT FOUND in store! Available groups: [${Array.from(this.groups.keys()).join(', ')}]`);
|
|
181
181
|
return;
|
|
182
182
|
}
|
|
183
|
+
if (!msgs || msgs.length === 0)
|
|
184
|
+
return;
|
|
183
185
|
utils_1.logger.log(`[GroupMessageStore] addMessages: group=${groupId} incoming=${msgs.length} currentLastMsgId=${data.record.lastMsgId} incomingMsgIds=[${msgs.map(m => m.msg_id).join(',')}]`);
|
|
184
186
|
let added = 0;
|
|
185
187
|
for (const msg of msgs) {
|
package/dist/group/operations.js
CHANGED
|
@@ -207,9 +207,10 @@ class GroupOperations {
|
|
|
207
207
|
let after = cursor.msg_cursor.current_msg_id;
|
|
208
208
|
while (true) {
|
|
209
209
|
const result = await this.pullMessages(targetAid, groupId, after, 50);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
const messages = result.messages || [];
|
|
211
|
+
if (messages.length > 0) {
|
|
212
|
+
handler.onMessages(groupId, messages);
|
|
213
|
+
const lastId = (_a = messages[messages.length - 1].msg_id) !== null && _a !== void 0 ? _a : after;
|
|
213
214
|
await this.ackMessages(targetAid, groupId, lastId);
|
|
214
215
|
after = lastId;
|
|
215
216
|
}
|
|
@@ -223,9 +224,10 @@ class GroupOperations {
|
|
|
223
224
|
let after = cursor.event_cursor.current_event_id;
|
|
224
225
|
while (true) {
|
|
225
226
|
const result = await this.pullEvents(targetAid, groupId, after, 50);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
const events = result.events || [];
|
|
228
|
+
if (events.length > 0) {
|
|
229
|
+
handler.onEvents(groupId, events);
|
|
230
|
+
const lastId = (_a = events[events.length - 1].event_id) !== null && _a !== void 0 ? _a : after;
|
|
229
231
|
await this.ackEvents(targetAid, groupId, lastId);
|
|
230
232
|
after = lastId;
|
|
231
233
|
}
|
package/dist/server.js
CHANGED
|
@@ -151,25 +151,27 @@ async function doEnsureOnline(aid) {
|
|
|
151
151
|
instance.agentWS.acceptInviteFromHeartbeat(invite.sessionId, invite.inviterAgentId, invite.inviteCode);
|
|
152
152
|
}
|
|
153
153
|
});
|
|
154
|
-
// 心跳重连成功后,自动触发 WebSocket 重连 +
|
|
154
|
+
// 心跳重连成功后,自动触发 WebSocket 重连 + 重新注册上线
|
|
155
155
|
hb.onReconnect(() => {
|
|
156
156
|
if (instance.agentWS) {
|
|
157
157
|
utils_1.logger.log('[Server] 心跳重连成功,触发 WebSocket 重连...');
|
|
158
158
|
instance.agentWS.reconnect().then(async () => {
|
|
159
|
-
// WebSocket 重连成功后,重新注册所有在线群组
|
|
160
|
-
// 断线期间 group.ap 会将在线状态过期,必须重新 register_online 才能收到推送
|
|
161
159
|
const onlineGroups = instance.agentCP.getOnlineGroups();
|
|
162
160
|
if (onlineGroups.length > 0) {
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
await instance.agentCP.groupRegisterOnline();
|
|
162
|
+
utils_1.logger.log(`[Server] WebSocket 重连成功,已重新注册上线,在线群组: ${onlineGroups.length}`);
|
|
163
|
+
// 后台异步拉取断线期间可能漏掉的消息
|
|
164
|
+
Promise.all(onlineGroups.map(async (groupId) => {
|
|
165
165
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
const lastMsgId = instance.agentCP.getGroupLastMsgId(groupId);
|
|
167
|
+
await instance.agentCP.pullAndStoreGroupMessages(groupId, lastMsgId, 50);
|
|
168
168
|
}
|
|
169
169
|
catch (e) {
|
|
170
|
-
utils_1.logger.warn(`[Server]
|
|
170
|
+
utils_1.logger.warn(`[Server] reconnect sync failed: ${groupId}`, e.message || e);
|
|
171
171
|
}
|
|
172
|
-
}
|
|
172
|
+
})).then(() => {
|
|
173
|
+
utils_1.logger.log(`[Server] 重连后台消息同步完成`);
|
|
174
|
+
});
|
|
173
175
|
}
|
|
174
176
|
}).catch((err) => {
|
|
175
177
|
utils_1.logger.error('[Server] WebSocket 重连失败:', err);
|
|
@@ -184,19 +186,22 @@ async function doEnsureOnline(aid) {
|
|
|
184
186
|
instance.connectionConfig = newConnConfig;
|
|
185
187
|
utils_1.logger.log('[Server] 重新鉴权成功,使用新 signature 重连 WebSocket...');
|
|
186
188
|
await instance.agentWS.reconnect(newConnConfig.messageServer, newConnConfig.messageSignature);
|
|
187
|
-
// 重连成功后重新注册所有在线群组
|
|
188
189
|
const onlineGroups = instance.agentCP.getOnlineGroups();
|
|
189
190
|
if (onlineGroups.length > 0) {
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
await instance.agentCP.groupRegisterOnline();
|
|
192
|
+
utils_1.logger.log(`[Server] 重新鉴权重连成功,已重新注册上线,在线群组: ${onlineGroups.length}`);
|
|
193
|
+
// 后台异步拉取断线期间可能漏掉的消息
|
|
194
|
+
Promise.all(onlineGroups.map(async (groupId) => {
|
|
192
195
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
const lastMsgId = instance.agentCP.getGroupLastMsgId(groupId);
|
|
197
|
+
await instance.agentCP.pullAndStoreGroupMessages(groupId, lastMsgId, 50);
|
|
195
198
|
}
|
|
196
199
|
catch (e) {
|
|
197
|
-
utils_1.logger.warn(`[Server]
|
|
200
|
+
utils_1.logger.warn(`[Server] reconnect sync failed: ${groupId}`, e.message || e);
|
|
198
201
|
}
|
|
199
|
-
}
|
|
202
|
+
})).then(() => {
|
|
203
|
+
utils_1.logger.log(`[Server] 重连后台消息同步完成`);
|
|
204
|
+
});
|
|
200
205
|
}
|
|
201
206
|
}
|
|
202
207
|
catch (err) {
|
|
@@ -230,7 +235,7 @@ async function doEnsureOnline(aid) {
|
|
|
230
235
|
try {
|
|
231
236
|
const parsed = JSON.parse(msgContent);
|
|
232
237
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
233
|
-
content = parsed.map((item) => item.content || '').join('');
|
|
238
|
+
content = parsed.map((item) => (item && item.content) || '').join('');
|
|
234
239
|
}
|
|
235
240
|
else if (parsed.content) {
|
|
236
241
|
content = parsed.content;
|
|
@@ -284,12 +289,33 @@ async function doEnsureOnline(aid) {
|
|
|
284
289
|
return instance;
|
|
285
290
|
}
|
|
286
291
|
// 确保群组客户端已初始化
|
|
292
|
+
// 群组客户端初始化锁,防止并发请求重复初始化
|
|
293
|
+
const groupInitPromises = new Map();
|
|
287
294
|
async function ensureGroupClient(instance) {
|
|
288
295
|
if (instance.groupInitialized && instance.agentCP.groupClient)
|
|
289
296
|
return;
|
|
290
297
|
if (!instance.agentWS)
|
|
291
298
|
throw new Error('WebSocket 未连接');
|
|
292
299
|
const aid = instance.aid;
|
|
300
|
+
// 如果已有初始化在进行中,等待它完成
|
|
301
|
+
const existing = groupInitPromises.get(aid);
|
|
302
|
+
if (existing) {
|
|
303
|
+
await existing;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const initPromise = doInitGroupClient(instance);
|
|
307
|
+
groupInitPromises.set(aid, initPromise);
|
|
308
|
+
try {
|
|
309
|
+
await initPromise;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
groupInitPromises.delete(aid);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async function doInitGroupClient(instance) {
|
|
316
|
+
if (!instance.agentWS)
|
|
317
|
+
throw new Error('WebSocket 未连接');
|
|
318
|
+
const aid = instance.aid;
|
|
293
319
|
// 计算 group target AID: group.{issuer}
|
|
294
320
|
const parts = aid.split('.', 1);
|
|
295
321
|
const issuer = aid.substring(parts[0].length + 1) || aid;
|
|
@@ -360,7 +386,7 @@ async function ensureGroupClient(instance) {
|
|
|
360
386
|
let groupName = groupId;
|
|
361
387
|
try {
|
|
362
388
|
const info = await instance.agentCP.groupOps.getGroupInfo(instance.groupTargetAid, groupId);
|
|
363
|
-
groupName = info.name || groupId;
|
|
389
|
+
groupName = (info && info.name) || groupId;
|
|
364
390
|
}
|
|
365
391
|
catch (_) { }
|
|
366
392
|
instance.agentCP.addGroupToStore(groupId, groupName);
|
|
@@ -386,7 +412,8 @@ async function ensureGroupClient(instance) {
|
|
|
386
412
|
broadcastToBrowser({ type: 'join_request', group_id: groupId, agent_id: agentId, message });
|
|
387
413
|
},
|
|
388
414
|
onGroupMessageBatch(groupId, batch) {
|
|
389
|
-
|
|
415
|
+
const batchMessages = batch.messages || [];
|
|
416
|
+
utils_1.logger.log(`[Group] onGroupMessageBatch: group=${groupId} count=${batch.count} range=[${batch.start_msg_id}, ${batch.latest_msg_id}] messages=${JSON.stringify(batchMessages.map(m => m.msg_id))}`);
|
|
390
417
|
// 存储 + ACK(统一由 agentcp 处理),注意 processAndAckBatch 是 async
|
|
391
418
|
instance.agentCP.processAndAckBatch(groupId, batch).then((sorted) => {
|
|
392
419
|
var _a, _b;
|
|
@@ -431,15 +458,27 @@ async function ensureGroupClient(instance) {
|
|
|
431
458
|
utils_1.logger.warn('[Group] syncGroupList error:', e.message);
|
|
432
459
|
}
|
|
433
460
|
}
|
|
434
|
-
//
|
|
461
|
+
// 注册上线 + 将群组加入在线列表(不阻塞)
|
|
435
462
|
const groups = instance.agentCP.getLocalGroupList();
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
463
|
+
if (groups.length > 0) {
|
|
464
|
+
await instance.agentCP.groupRegisterOnline();
|
|
465
|
+
for (const group of groups) {
|
|
466
|
+
instance.agentCP.addOnlineGroup(group.group_id);
|
|
467
|
+
}
|
|
468
|
+
instance.agentCP.ensureGroupHeartbeat();
|
|
469
|
+
utils_1.logger.log(`[Group] 已注册上线,在线群组: ${groups.length}`);
|
|
470
|
+
// 后台异步拉取历史消息,不阻塞启动流程
|
|
471
|
+
Promise.all(groups.map(async (group) => {
|
|
472
|
+
try {
|
|
473
|
+
const lastMsgId = instance.agentCP.getGroupLastMsgId(group.group_id);
|
|
474
|
+
await instance.agentCP.pullAndStoreGroupMessages(group.group_id, lastMsgId, 50);
|
|
475
|
+
}
|
|
476
|
+
catch (e) {
|
|
477
|
+
utils_1.logger.warn(`[Group] background sync failed: ${group.group_id}`, e.message);
|
|
478
|
+
}
|
|
479
|
+
})).then(() => {
|
|
480
|
+
utils_1.logger.log(`[Group] 后台消息同步完成,共 ${groups.length} 个群组`);
|
|
481
|
+
});
|
|
443
482
|
}
|
|
444
483
|
instance.groupInitialized = true;
|
|
445
484
|
instance.groupSessionId = groupSessionId;
|
|
@@ -480,7 +519,7 @@ async function getAidStatusList() {
|
|
|
480
519
|
const agentInfoCache = new Map();
|
|
481
520
|
const AGENT_INFO_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
482
521
|
function getAgentInfoCachePath() {
|
|
483
|
-
const dir = globalDataDir ||
|
|
522
|
+
const dir = globalDataDir || datamanager_1.DEFAULT_ACP_DIR;
|
|
484
523
|
return path.join(dir, 'AIDs', '.agent-info-cache.json');
|
|
485
524
|
}
|
|
486
525
|
function loadAgentInfoCacheFromDisk() {
|
|
@@ -579,7 +618,7 @@ async function getAgentInfo(aid) {
|
|
|
579
618
|
}
|
|
580
619
|
// 每个 AID 的自定义 agent.md 选项 (昵称、描述)
|
|
581
620
|
function getAidMdOptionsPath() {
|
|
582
|
-
const dir = globalDataDir ||
|
|
621
|
+
const dir = globalDataDir || datamanager_1.DEFAULT_ACP_DIR;
|
|
583
622
|
return path.join(dir, 'AIDs', '.aid-md-options.json');
|
|
584
623
|
}
|
|
585
624
|
function loadAidMdOptions() {
|
|
@@ -614,7 +653,7 @@ function getMessageStoreForAid(aid) {
|
|
|
614
653
|
if (!store) {
|
|
615
654
|
store = new messagestore_1.MessageStore({
|
|
616
655
|
persistMessages: true,
|
|
617
|
-
basePath: globalDataDir ||
|
|
656
|
+
basePath: globalDataDir || datamanager_1.DEFAULT_ACP_DIR,
|
|
618
657
|
});
|
|
619
658
|
messageStores.set(aid, store);
|
|
620
659
|
}
|
|
@@ -638,6 +677,10 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
638
677
|
<title>ACP 身份管理</title>
|
|
639
678
|
<style>
|
|
640
679
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
680
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
681
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
682
|
+
::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 3px; }
|
|
683
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.25); }
|
|
641
684
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #f0f4ff 0%, #e8edf5 100%); min-height: 100vh; display: flex; justify-content: center; align-items: flex-start; padding: 40px 20px; }
|
|
642
685
|
.container { background: white; padding: 0; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.08); max-width: 560px; width: 100%; overflow: hidden; }
|
|
643
686
|
.page-header { background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%); padding: 28px 32px 22px; color: white; text-align: center; }
|
|
@@ -674,6 +717,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
674
717
|
.create-section .extra-fields input { flex: 1; padding: 10px 14px; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 14px; min-width: 0; background: #fff; transition: border-color 0.2s, box-shadow 0.2s; }
|
|
675
718
|
.create-section .extra-fields input:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,0.1); }
|
|
676
719
|
.btn { display: block; width: 100%; padding: 11px; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; }
|
|
720
|
+
.btn:active { transform: scale(0.98); }
|
|
677
721
|
.btn-primary { background: linear-gradient(135deg, #2563eb, #1d4ed8); color: white; }
|
|
678
722
|
.btn-primary:hover { background: linear-gradient(135deg, #1d4ed8, #1e40af); box-shadow: 0 2px 8px rgba(37,99,235,0.3); }
|
|
679
723
|
.btn-sm { display: inline-block; width: auto; padding: 6px 14px; font-size: 13px; border-radius: 6px; }
|
|
@@ -684,10 +728,10 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
684
728
|
.btn-outline { background: white; color: #2563eb; border: 1px solid #2563eb; }
|
|
685
729
|
.btn-outline:hover { background: #eff6ff; }
|
|
686
730
|
.btn-outline.active { background: #2563eb; color: white; }
|
|
687
|
-
.btn:disabled { background: #d1d5db; cursor: not-allowed; border-color: #d1d5db; color: #fff; }
|
|
731
|
+
.btn:disabled { background: #d1d5db; cursor: not-allowed; border-color: #d1d5db; color: #fff; transform: none; }
|
|
688
732
|
.aid-list { margin-bottom: 24px; }
|
|
689
733
|
.aid-card { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px; margin-bottom: 10px; transition: all 0.2s; display: flex; align-items: stretch; gap: 12px; }
|
|
690
|
-
.aid-card:hover { border-color: #93c5fd; box-shadow: 0
|
|
734
|
+
.aid-card:hover { border-color: #93c5fd; box-shadow: 0 4px 12px rgba(37,99,235,0.08); transform: translateY(-1px); }
|
|
691
735
|
.aid-card.current { border-color: #2563eb; background: #eff6ff; }
|
|
692
736
|
.aid-card-left { flex: 1; min-width: 0; }
|
|
693
737
|
.aid-card-right { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; justify-content: center; }
|
|
@@ -703,9 +747,9 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
703
747
|
.badge-info { background: #dbeafe; color: #1e40af; }
|
|
704
748
|
.badge-current { background: #2563eb; color: white; }
|
|
705
749
|
.aid-card-actions { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
|
|
706
|
-
.status { position: fixed; top: 20px; left: 50%; transform:
|
|
707
|
-
.status.success {
|
|
708
|
-
.status.error {
|
|
750
|
+
.status { position: fixed; top: 20px; left: 50%; transform: translate(-50%, -10px); padding: 12px 24px; border-radius: 10px; font-size: 14px; opacity: 0; visibility: hidden; z-index: 1000; box-shadow: 0 4px 16px rgba(0,0,0,0.1); transition: all 0.3s cubic-bezier(0.16,1,0.3,1); }
|
|
751
|
+
.status.success { opacity: 1; visibility: visible; transform: translate(-50%, 0); background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
|
|
752
|
+
.status.error { opacity: 1; visibility: visible; transform: translate(-50%, 0); background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
|
|
709
753
|
@media (max-width: 480px) {
|
|
710
754
|
body { padding: 16px 8px; }
|
|
711
755
|
.page-header { padding: 22px 18px 18px; }
|
|
@@ -763,7 +807,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
763
807
|
function updateApSelect() {
|
|
764
808
|
var sel = document.getElementById('apSelect');
|
|
765
809
|
if (sel && sel.options.length === 0) {
|
|
766
|
-
const options = ['agentcp.io', '
|
|
810
|
+
const options = ['agentcp.io', 'agentid.pub'];
|
|
767
811
|
options.forEach(function(op) {
|
|
768
812
|
var opt = document.createElement('option');
|
|
769
813
|
opt.value = op;
|
|
@@ -864,6 +908,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
864
908
|
var data = await res.json();
|
|
865
909
|
if (data.success) {
|
|
866
910
|
showStatus(aid + ' 已上线,正在进入聊天...', 'success');
|
|
911
|
+
sessionStorage.setItem('chatEntry','1');
|
|
867
912
|
window.location.href = '/chat';
|
|
868
913
|
} else {
|
|
869
914
|
showStatus(data.error || '上线失败', 'error');
|
|
@@ -875,7 +920,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
875
920
|
}
|
|
876
921
|
}
|
|
877
922
|
|
|
878
|
-
function enterChat(aid) { window.location.href = '/chat'; }
|
|
923
|
+
function enterChat(aid) { sessionStorage.setItem('chatEntry','1'); window.location.href = '/chat'; }
|
|
879
924
|
|
|
880
925
|
async function goOffline(aid) {
|
|
881
926
|
try {
|
|
@@ -892,11 +937,13 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
892
937
|
});
|
|
893
938
|
}
|
|
894
939
|
|
|
940
|
+
let statusTimeout = null;
|
|
895
941
|
function showStatus(msg, type) {
|
|
896
942
|
var el = document.getElementById('status');
|
|
897
943
|
el.textContent = msg;
|
|
898
944
|
el.className = 'status ' + type;
|
|
899
|
-
|
|
945
|
+
if (statusTimeout) clearTimeout(statusTimeout);
|
|
946
|
+
statusTimeout = setTimeout(function() { el.className = 'status'; }, 3000);
|
|
900
947
|
}
|
|
901
948
|
|
|
902
949
|
function escapeHtml(text) {
|
|
@@ -924,6 +971,10 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
924
971
|
<style>
|
|
925
972
|
:root { --primary:#2563eb; --primary-h:#1d4ed8; --bg:#f3f4f6; --sidebar-bg:#fff; --chat-bg:#f9fafb; --border:#e5e7eb; --t1:#1f2937; --t2:#6b7280; --sent:#2563eb; --recv-bg:#fff; --ok:#10b981; }
|
|
926
973
|
* { box-sizing:border-box; margin:0; padding:0; }
|
|
974
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
975
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
976
|
+
::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 3px; }
|
|
977
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.25); }
|
|
927
978
|
body { font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; background:var(--bg); height:100vh; overflow:hidden; color:var(--t1); }
|
|
928
979
|
#app { display:flex; height:100%; }
|
|
929
980
|
|
|
@@ -933,8 +984,9 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
933
984
|
.sidebar-header { padding:12px 14px; border-bottom:1px solid var(--border); display:flex; flex-direction:column; gap:12px; flex-shrink:0; }
|
|
934
985
|
.header-top { display:flex; justify-content:space-between; align-items:center; width:100%; }
|
|
935
986
|
.sidebar-header .my-aid { font-size:11px; color:#155724; font-family:monospace; background:#d4edda; padding:4px 8px; border-radius:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; border:1px solid #c3e6cb; flex:1; margin-right:8px; }
|
|
936
|
-
.new-chat-btn { padding:8px 10px; background:var(--primary); color:#fff; border:none; border-radius:6px; font-size:12px; cursor:pointer; white-space:nowrap; width:100%; text-align:center; }
|
|
987
|
+
.new-chat-btn { padding:8px 10px; background:var(--primary); color:#fff; border:none; border-radius:6px; font-size:12px; cursor:pointer; white-space:nowrap; width:100%; text-align:center; transition:all 0.15s; }
|
|
937
988
|
.new-chat-btn:hover { background:var(--primary-h); }
|
|
989
|
+
.new-chat-btn:active { transform:scale(0.96); }
|
|
938
990
|
.session-list { flex:1; overflow-y:auto; }
|
|
939
991
|
|
|
940
992
|
/* AID Group */
|
|
@@ -947,12 +999,12 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
947
999
|
.aid-group-arrow { font-size:10px; color:var(--primary); transition:transform 0.2s; flex-shrink:0; }
|
|
948
1000
|
.aid-group-arrow.open { transform:rotate(90deg); }
|
|
949
1001
|
.aid-group-badge { font-size:10px; background:var(--primary); color:#fff; padding:1px 6px; border-radius:8px; margin-left:8px; flex-shrink:0; }
|
|
950
|
-
.aid-group-add { background:none; border:1px solid var(--border); color:var(--t2); width:22px; height:22px; border-radius:4px; cursor:pointer; font-size:14px; line-height:20px; text-align:center; margin-left:6px; flex-shrink:0; }
|
|
1002
|
+
.aid-group-add { background:none; border:1px solid var(--border); color:var(--t2); width:22px; height:22px; border-radius:4px; cursor:pointer; font-size:14px; line-height:20px; text-align:center; margin-left:6px; flex-shrink:0; transition:all 0.15s; }
|
|
951
1003
|
.aid-group-add:hover { background:var(--primary); color:#fff; border-color:var(--primary); }
|
|
952
|
-
.aid-group-del { background:none; border:none; color:var(--t2); width:20px; height:20px; border-radius:4px; cursor:pointer; font-size:12px; line-height:20px; text-align:center; margin-left:4px; flex-shrink:0; display:none; }
|
|
1004
|
+
.aid-group-del { background:none; border:none; color:var(--t2); width:20px; height:20px; border-radius:4px; cursor:pointer; font-size:12px; line-height:20px; text-align:center; margin-left:4px; flex-shrink:0; display:none; transition:all 0.15s; }
|
|
953
1005
|
.aid-group-header:hover .aid-group-del { display:block; }
|
|
954
1006
|
.aid-group-del:hover { color:#dc3545; background:#ffebeb; }
|
|
955
|
-
.session-del { position:absolute; right:8px; top:50%; transform:translateY(-50%); background:none; border:none; color:var(--t2); font-size:12px; cursor:pointer; display:none; padding:2px; }
|
|
1007
|
+
.session-del { position:absolute; right:8px; top:50%; transform:translateY(-50%); background:none; border:none; color:var(--t2); font-size:12px; cursor:pointer; display:none; padding:2px; transition:all 0.15s; }
|
|
956
1008
|
.session-item:hover .session-del { display:block; }
|
|
957
1009
|
.session-del:hover { color:#dc3545; }
|
|
958
1010
|
.aid-group-sessions { display:none; background:#fafbfc; }
|
|
@@ -961,11 +1013,11 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
961
1013
|
.aid-group-avatar { width:36px; height:36px; border-radius:50%; object-fit:cover; flex-shrink:0; margin-right:8px; box-shadow:0 1px 4px rgba(37,99,235,0.18); border:2px solid #bfdbfe; }
|
|
962
1014
|
|
|
963
1015
|
.session-item { padding:10px 14px 10px 32px; border-bottom:1px solid #f0f1f3; cursor:pointer; transition:all 0.15s; position:relative; }
|
|
964
|
-
.session-item::before { content:''; position:absolute; left:18px; top:50%; transform:translateY(-50%); width:6px; height:6px; border-radius:50%; background:#d1d5db; }
|
|
1016
|
+
.session-item::before { content:''; position:absolute; left:18px; top:50%; transform:translateY(-50%); width:6px; height:6px; border-radius:50%; background:#d1d5db; transition:all 0.15s; }
|
|
965
1017
|
.session-item:hover { background:#f0f5ff; }
|
|
966
1018
|
.session-item.active { background:#eff6ff; border-left:3px solid var(--primary); padding-left:29px; }
|
|
967
1019
|
.session-item.active::before { background:var(--primary); box-shadow:0 0 0 2px rgba(37,99,235,0.2); }
|
|
968
|
-
.session-peer { font-weight:500; font-size:12px; color:var(--t1); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; padding-left:10px; background:#f1f5f9; border-radius:4px; padding:3px 8px 3px 10px; border:1px solid #e8ecf1; }
|
|
1020
|
+
.session-peer { font-weight:500; font-size:12px; color:var(--t1); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; padding-left:10px; background:#f1f5f9; border-radius:4px; padding:3px 8px 3px 10px; border:1px solid #e8ecf1; transition:all 0.15s; }
|
|
969
1021
|
.session-item.active .session-peer { background:#dbeafe; border-color:#bfdbfe; color:#1e40af; }
|
|
970
1022
|
.session-meta { font-size:10px; color:var(--t2); margin-top:4px; display:flex; align-items:center; gap:6px; padding-left:10px; }
|
|
971
1023
|
.tag { font-size:9px; padding:1px 5px; border-radius:3px; color:#fff; font-weight:600; letter-spacing:0.3px; }
|
|
@@ -976,16 +1028,17 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
976
1028
|
.chat-area { flex:1; display:flex; flex-direction:column; background:var(--chat-bg); min-width:0; }
|
|
977
1029
|
.chat-header { height:54px; padding:0 16px; background:#fff; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between; flex-shrink:0; }
|
|
978
1030
|
.header-left { display:flex; align-items:center; gap:10px; overflow:hidden; }
|
|
979
|
-
.toggle-sidebar-btn { background:none; border:none; cursor:pointer; color:var(--t2); padding:4px; display:flex; }
|
|
1031
|
+
.toggle-sidebar-btn { background:none; border:none; cursor:pointer; color:var(--t2); padding:4px; display:flex; transition:all 0.15s; }
|
|
980
1032
|
.toggle-sidebar-btn:hover { color:var(--t1); }
|
|
981
|
-
.status-dot { width:8px; height:8px; border-radius:50%; background:#ccc; flex-shrink:0; }
|
|
982
|
-
.status-dot.connected { background:var(--ok); }
|
|
1033
|
+
.status-dot { width:8px; height:8px; border-radius:50%; background:#ccc; flex-shrink:0; transition:all 0.3s; }
|
|
1034
|
+
.status-dot.connected { background:var(--ok); box-shadow:0 0 0 2px rgba(16,185,129,0.2); }
|
|
983
1035
|
.status-dot.connecting { background:#fbbf24; }
|
|
984
1036
|
.chat-title { font-size:15px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
985
1037
|
|
|
986
1038
|
.aid-select-wrap { display:flex; align-items:center; gap:10px; flex-shrink:0; }
|
|
987
1039
|
.manage-btn { display:flex; align-items:center; gap:4px; text-decoration:none; color:var(--t2); font-size:12px; padding:6px 10px; border-radius:6px; transition:all 0.2s; background:#fff; border:1px solid var(--border); }
|
|
988
1040
|
.manage-btn:hover { background:#f8fafc; color:var(--primary); border-color:var(--primary); }
|
|
1041
|
+
.manage-btn:active { transform:scale(0.96); }
|
|
989
1042
|
.aid-control-group { display:flex; align-items:center; background:#fff; border:1px solid var(--border); border-radius:6px; padding:2px; box-shadow:0 1px 2px rgba(0,0,0,0.03); }
|
|
990
1043
|
.aid-select { border:none; background:transparent; font-size:12px; color:var(--t1); padding:5px 8px; outline:none; cursor:pointer; min-width:120px; font-weight:500; }
|
|
991
1044
|
.status-toggle { display:flex; align-items:center; gap:5px; padding:4px 8px; border-radius:4px; cursor:pointer; font-size:11px; margin-left:2px; transition:background 0.2s; user-select:none; border-left:1px solid var(--border); }
|
|
@@ -994,7 +1047,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
994
1047
|
.status-indicator.online { background:var(--ok); box-shadow:0 0 0 2px rgba(16,185,129,0.2); }
|
|
995
1048
|
.status-indicator.offline { background:#cbd5e1; }
|
|
996
1049
|
|
|
997
|
-
.collapse-btn { background:none; border:none; cursor:pointer; color:var(--t2); padding:6px; display:flex; align-items:center; flex-shrink:0; }
|
|
1050
|
+
.collapse-btn { background:none; border:none; cursor:pointer; color:var(--t2); padding:6px; display:flex; align-items:center; flex-shrink:0; transition:all 0.15s; }
|
|
998
1051
|
.collapse-btn:hover { color:var(--t1); }
|
|
999
1052
|
|
|
1000
1053
|
.encrypt-banner { background:linear-gradient(135deg,#e0f2fe,#dbeafe); border:1px solid #bae6fd; border-radius:8px; padding:8px 14px; margin:8px 16px 0; display:flex; align-items:center; gap:8px; font-size:11px; color:#0369a1; flex-shrink:0; }
|
|
@@ -1004,24 +1057,34 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1004
1057
|
.message { display:flex; flex-direction:column; max-width:80%; }
|
|
1005
1058
|
.message.sent { align-self:flex-end; align-items:flex-end; }
|
|
1006
1059
|
.message.received { align-self:flex-start; align-items:flex-start; }
|
|
1007
|
-
.bubble { padding:10px 14px; border-radius:
|
|
1008
|
-
.message.sent .bubble { background:var(--sent); color:#fff; border-bottom-right-radius:
|
|
1009
|
-
.message.received .bubble { background:var(--recv-bg); color:var(--t1); border-bottom-left-radius:
|
|
1060
|
+
.bubble { padding:10px 14px; border-radius:14px; font-size:14.5px; line-height:1.6; word-wrap:break-word; box-shadow:0 1px 2px rgba(0,0,0,0.05); }
|
|
1061
|
+
.message.sent .bubble { background:var(--sent); color:#fff; border-bottom-right-radius:4px; }
|
|
1062
|
+
.message.received .bubble { background:var(--recv-bg); color:var(--t1); border-bottom-left-radius:4px; border:1px solid var(--border); box-shadow:0 1px 3px rgba(0,0,0,0.04); }
|
|
1010
1063
|
.msg-meta { font-size:10px; color:var(--t2); margin-bottom:3px; padding:0 4px; }
|
|
1011
1064
|
|
|
1012
|
-
.input-area { padding:12px 16px; background:#fff; border-top:1px solid var(--border); display:flex;
|
|
1013
|
-
.input-area
|
|
1014
|
-
.input-
|
|
1015
|
-
.
|
|
1065
|
+
.input-area { padding:12px 16px; background:#fff; border-top:1px solid var(--border); display:flex; flex-direction:column; gap:8px; flex-shrink:0; }
|
|
1066
|
+
.input-area.drag-over { background:#eff6ff; border-top-color:var(--primary); }
|
|
1067
|
+
.input-row { display:flex; align-items:flex-end; gap:10px; }
|
|
1068
|
+
.input-area textarea { flex:1; padding:10px 14px; border-radius:12px; border:1px solid var(--border); font-size:14px; background:#f9fafb; transition:all 0.2s; resize:none; line-height:1.5; min-height:63px; max-height:105px; overflow-y:auto; font-family:inherit; }
|
|
1069
|
+
.input-area textarea:focus { outline:none; border-color:var(--primary); background:#fff; box-shadow:0 0 0 3px rgba(37,99,235,0.1); }
|
|
1070
|
+
.file-list { display:flex; flex-wrap:wrap; gap:6px; padding:4px 0; }
|
|
1071
|
+
.file-item { display:flex; align-items:center; gap:6px; background:#f0f4ff; border:1px solid #d0d9f0; border-radius:8px; padding:4px 8px; font-size:12px; color:var(--t1); max-width:220px; }
|
|
1072
|
+
.file-item .file-icon { flex-shrink:0; width:16px; height:16px; color:var(--primary); }
|
|
1073
|
+
.file-item .file-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1074
|
+
.file-item .file-remove { flex-shrink:0; width:16px; height:16px; cursor:pointer; color:#999; border:none; background:none; padding:0; display:flex; align-items:center; justify-content:center; border-radius:50%; transition:all 0.15s; }
|
|
1075
|
+
.file-item .file-remove:hover { color:#e53e3e; background:rgba(229,62,62,0.1); }
|
|
1076
|
+
.send-btn { width:40px; height:40px; border-radius:50%; background:var(--primary); border:none; color:#fff; display:flex; align-items:center; justify-content:center; cursor:pointer; flex-shrink:0; transition:all 0.15s; }
|
|
1016
1077
|
.send-btn:hover { background:var(--primary-h); }
|
|
1017
|
-
.send-btn:
|
|
1078
|
+
.send-btn:active { transform:scale(0.94); }
|
|
1079
|
+
.send-btn:disabled { background:#ccc; cursor:not-allowed; transform:none; }
|
|
1018
1080
|
|
|
1019
|
-
.modal-overlay { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:50; display:
|
|
1020
|
-
.modal-overlay.show {
|
|
1021
|
-
.modal { background:#fff; width:90%; max-width:400px; border-radius:12px; padding:24px; box-shadow:0 10px 25px rgba(0,0,0,0.1); }
|
|
1081
|
+
.modal-overlay { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:50; display:flex; align-items:center; justify-content:center; opacity:0; visibility:hidden; transition:all 0.2s ease-out; backdrop-filter:blur(2px); }
|
|
1082
|
+
.modal-overlay.show { opacity:1; visibility:visible; }
|
|
1083
|
+
.modal { background:#fff; width:90%; max-width:400px; border-radius:12px; padding:24px; box-shadow:0 10px 25px rgba(0,0,0,0.15); transform:scale(0.95) translateY(10px); transition:all 0.2s cubic-bezier(0.16,1,0.3,1); }
|
|
1084
|
+
.modal-overlay.show .modal { transform:scale(1) translateY(0); }
|
|
1022
1085
|
.modal h3 { margin-bottom:16px; font-size:16px; }
|
|
1023
|
-
.modal input[type="text"], .modal input[type="password"], .modal input[type="url"] { width:100%; padding:10px; border:1px solid var(--border); border-radius:8px; margin-bottom:16px; font-size:14px; box-sizing:border-box; }
|
|
1024
|
-
.modal input[type="text"]:focus, .modal input[type="password"]:focus, .modal input[type="url"]:focus { outline:none; border-color:var(--primary); }
|
|
1086
|
+
.modal input[type="text"], .modal input[type="password"], .modal input[type="url"] { width:100%; padding:10px; border:1px solid var(--border); border-radius:8px; margin-bottom:16px; font-size:14px; box-sizing:border-box; transition:all 0.2s; }
|
|
1087
|
+
.modal input[type="text"]:focus, .modal input[type="password"]:focus, .modal input[type="url"]:focus { outline:none; border-color:var(--primary); box-shadow:0 0 0 3px rgba(37,99,235,0.1); }
|
|
1025
1088
|
.modal input[type="radio"] { width:auto; margin:0; }
|
|
1026
1089
|
.group-type-card { flex:1; padding:12px; border:2px solid var(--border); border-radius:10px; cursor:pointer; transition:all 0.2s; background:#fafafa; }
|
|
1027
1090
|
.group-type-card:hover { border-color:#b0b0b0; background:#f5f5f5; }
|
|
@@ -1030,10 +1093,11 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1030
1093
|
.duty-rule-card:hover { border-color:#b0b0b0; background:#f5f5f5; }
|
|
1031
1094
|
.duty-rule-card.selected { border-color:var(--primary); background:rgba(0,122,255,0.06); }
|
|
1032
1095
|
.modal-btns { display:flex; justify-content:flex-end; gap:10px; }
|
|
1033
|
-
.mbtn { padding:8px 16px; border-radius:6px; font-size:13px; cursor:pointer; border:none; }
|
|
1096
|
+
.mbtn { padding:8px 16px; border-radius:6px; font-size:13px; cursor:pointer; border:none; transition:all 0.15s; }
|
|
1097
|
+
.mbtn:active { transform:scale(0.96); }
|
|
1034
1098
|
.mbtn-cancel { background:#f3f4f6; color:var(--t1); }
|
|
1035
1099
|
.mbtn-ok { background:var(--primary); color:#fff; }
|
|
1036
|
-
.mbtn-ok:disabled { background:#ccc; }
|
|
1100
|
+
.mbtn-ok:disabled { background:#ccc; transform:none; }
|
|
1037
1101
|
|
|
1038
1102
|
.bubble p { margin-bottom:0.4em; } .bubble p:last-child { margin-bottom:0; }
|
|
1039
1103
|
.bubble h1, .bubble h2, .bubble h3, .bubble h4, .bubble h5, .bubble h6 { font-weight:600; line-height:1.25; margin-top:1em; margin-bottom:0.5em; color:inherit; }
|
|
@@ -1049,6 +1113,16 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1049
1113
|
.bubble code { background:rgba(0,0,0,0.1); padding:2px 4px; border-radius:3px; font-family:monospace; font-size:0.9em; }
|
|
1050
1114
|
.bubble pre { background:#2d2d2d; color:#fff; padding:12px; border-radius:6px; overflow-x:auto; margin:8px 0; }
|
|
1051
1115
|
.bubble pre code { background:transparent; padding:0; color:inherit; border-radius:0; }
|
|
1116
|
+
.bubble table { border-collapse:collapse; width:100%; margin:8px 0; font-size:0.9em; }
|
|
1117
|
+
.bubble th, .bubble td { border:1px solid rgba(0,0,0,0.15); padding:6px 10px; text-align:left; }
|
|
1118
|
+
.bubble th { background:rgba(0,0,0,0.05); font-weight:600; }
|
|
1119
|
+
.bubble hr { border:none; border-top:1px solid rgba(0,0,0,0.1); margin:0.8em 0; }
|
|
1120
|
+
.message.sent .bubble blockquote { color:rgba(255,255,255,0.8); border-left-color:rgba(255,255,255,0.4); }
|
|
1121
|
+
.message.sent .bubble code { background:rgba(255,255,255,0.2); }
|
|
1122
|
+
.message.sent .bubble th, .message.sent .bubble td { border-color:rgba(255,255,255,0.3); }
|
|
1123
|
+
.message.sent .bubble th { background:rgba(255,255,255,0.1); }
|
|
1124
|
+
.message.sent .bubble hr { border-top-color:rgba(255,255,255,0.3); }
|
|
1125
|
+
.message.sent .bubble h1, .message.sent .bubble h2 { border-bottom-color:rgba(255,255,255,0.2); }
|
|
1052
1126
|
.bubble-wrap { position:relative; }
|
|
1053
1127
|
.bubble-wrap .copy-msg-btn { position:absolute; top:4px; right:4px; opacity:0; pointer-events:none; background:rgba(0,0,0,0.45); color:#fff; border:none; border-radius:4px; padding:2px 6px; font-size:11px; cursor:pointer; line-height:1.4; z-index:1; transition:opacity 0.15s; }
|
|
1054
1128
|
.bubble-wrap:hover .copy-msg-btn { opacity:1; pointer-events:auto; }
|
|
@@ -1085,14 +1159,15 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1085
1159
|
.group-item.active { background:#eff6ff; border-left:3px solid var(--primary); padding-left:11px; }
|
|
1086
1160
|
.group-item-name { font-size:13px; font-weight:600; color:var(--t1); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1087
1161
|
.group-item-meta { font-size:10px; color:var(--t2); margin-top:2px; }
|
|
1088
|
-
.group-item-del { position:absolute; right:8px; top:12px; background:none; border:none; color:var(--t2); font-size:12px; cursor:pointer; display:none; padding:2px; }
|
|
1162
|
+
.group-item-del { position:absolute; right:8px; top:12px; background:none; border:none; color:var(--t2); font-size:12px; cursor:pointer; display:none; padding:2px; transition:all 0.15s; }
|
|
1089
1163
|
.group-item:hover .group-item-del { display:block; }
|
|
1090
1164
|
.group-item-del:hover { color:#dc3545; }
|
|
1091
1165
|
.group-actions { padding:8px 14px; display:flex; gap:6px; flex-shrink:0; border-bottom:1px solid var(--border); }
|
|
1092
|
-
.group-actions .gbtn { flex:1; padding:6px 0; border:1px solid var(--border); border-radius:6px; font-size:11px; cursor:pointer; background:#fff; color:var(--t1); text-align:center; }
|
|
1166
|
+
.group-actions .gbtn { flex:1; padding:6px 0; border:1px solid var(--border); border-radius:6px; font-size:11px; cursor:pointer; background:#fff; color:var(--t1); text-align:center; transition:all 0.15s; }
|
|
1093
1167
|
.group-actions .gbtn:hover { background:#f1f5f9; border-color:var(--primary); color:var(--primary); }
|
|
1168
|
+
.group-actions .gbtn:active { transform:scale(0.96); }
|
|
1094
1169
|
.group-info-bar { padding:6px 16px; background:#f0f9ff; border-bottom:1px solid #bae6fd; font-size:11px; color:#0369a1; display:flex; align-items:center; gap:8px; flex-shrink:0; }
|
|
1095
|
-
.group-info-bar .copy-link { cursor:pointer; text-decoration:underline; }
|
|
1170
|
+
.group-info-bar .copy-link { cursor:pointer; text-decoration:underline; transition:all 0.15s; }
|
|
1096
1171
|
.group-info-bar .copy-link:hover { color:#0284c7; }
|
|
1097
1172
|
</style>
|
|
1098
1173
|
<!-- CHATHTML_STYLE_END -->
|
|
@@ -1113,12 +1188,12 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1113
1188
|
</div>
|
|
1114
1189
|
</div>
|
|
1115
1190
|
<!-- P2P panel -->
|
|
1116
|
-
<div id="p2pPanel">
|
|
1191
|
+
<div id="p2pPanel" style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
|
|
1117
1192
|
<div style="padding:8px 14px;flex-shrink:0;"><button class="new-chat-btn" onclick="showModal()">+ 连接龙虾</button></div>
|
|
1118
1193
|
<div class="session-list" id="sessionList"></div>
|
|
1119
1194
|
</div>
|
|
1120
1195
|
<!-- Group panel -->
|
|
1121
|
-
<div id="groupPanel" style="display:none;flex
|
|
1196
|
+
<div id="groupPanel" style="display:none;flex-direction:column;overflow:hidden;">
|
|
1122
1197
|
<div class="group-actions">
|
|
1123
1198
|
<div class="gbtn" onclick="showCreateGroupModal()">创建群组</div>
|
|
1124
1199
|
<div class="gbtn" onclick="showJoinGroupModal()">加入群组</div>
|
|
@@ -1158,6 +1233,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1158
1233
|
<div class="group-info-bar" id="groupInfoBar" style="display:none;">
|
|
1159
1234
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
|
1160
1235
|
<span id="groupInfoText">群组</span>
|
|
1236
|
+
<span id="groupMemberStats" style="font-size:11px;color:var(--t2);margin-left:4px;"></span>
|
|
1161
1237
|
<span class="copy-link" id="groupInviteBtn" onclick="generateInviteLink()" title="生成邀请链接" style="display:none;">生成邀请链接</span>
|
|
1162
1238
|
<span class="copy-link" id="groupCopyLinkBtn" onclick="copyGroupLink()" title="复制群链接" style="display:none;">复制群链接</span>
|
|
1163
1239
|
<span class="copy-link" onclick="showGroupMembers()" title="查看成员">成员</span>
|
|
@@ -1173,11 +1249,14 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1173
1249
|
</div>
|
|
1174
1250
|
<div class="new-msg-tip" id="newMsgTip" onclick="scrollToBottom()">↓ 有新消息</div>
|
|
1175
1251
|
</div>
|
|
1176
|
-
<div class="input-area">
|
|
1177
|
-
<
|
|
1252
|
+
<div class="input-area" id="inputArea">
|
|
1253
|
+
<div class="file-list" id="fileList" style="display:none;"></div>
|
|
1254
|
+
<div class="input-row">
|
|
1255
|
+
<textarea id="messageInput" rows="3" placeholder="输入消息... 可拖入文件" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMessage();}" oninput="autoResizeInput()"></textarea>
|
|
1178
1256
|
<button class="send-btn" id="sendBtn" onclick="sendMessage()">
|
|
1179
1257
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"></path></svg>
|
|
1180
1258
|
</button>
|
|
1259
|
+
</div>
|
|
1181
1260
|
</div>
|
|
1182
1261
|
</div>
|
|
1183
1262
|
</div>
|
|
@@ -1326,13 +1405,13 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1326
1405
|
</div>
|
|
1327
1406
|
</div>
|
|
1328
1407
|
</div>
|
|
1329
|
-
<div id="switchAidOverlay" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(
|
|
1330
|
-
<div style="width:36px;height:36px;border:3px solid rgba(
|
|
1331
|
-
<div id="switchAidMsg" style="color
|
|
1408
|
+
<div id="switchAidOverlay" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(255,255,255,0.7);backdrop-filter:blur(4px);z-index:999;display:none;align-items:center;justify-content:center;flex-direction:column;gap:16px;transition:opacity 0.2s;">
|
|
1409
|
+
<div style="width:36px;height:36px;border:3px solid rgba(37,99,235,0.2);border-top-color:var(--primary);border-radius:50%;animation:spin 0.8s linear infinite;"></div>
|
|
1410
|
+
<div id="switchAidMsg" style="color:var(--t1);font-size:15px;font-weight:500;">切换身份中...</div>
|
|
1332
1411
|
</div>
|
|
1333
|
-
<div id="switchGroupOverlay" style="position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(
|
|
1334
|
-
<div style="width:32px;height:32px;border:3px solid rgba(
|
|
1335
|
-
<div id="switchGroupMsg" style="color
|
|
1412
|
+
<div id="switchGroupOverlay" style="position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(255,255,255,0.95);z-index:999;display:none;align-items:center;justify-content:center;flex-direction:column;gap:16px;padding:32px 48px;border-radius:16px;box-shadow:0 10px 40px rgba(0,0,0,0.1);border:1px solid rgba(0,0,0,0.05);">
|
|
1413
|
+
<div style="width:32px;height:32px;border:3px solid rgba(37,99,235,0.2);border-top-color:var(--primary);border-radius:50%;animation:spin 0.8s linear infinite;"></div>
|
|
1414
|
+
<div id="switchGroupMsg" style="color:var(--t1);font-size:14px;font-weight:500;">加载群组中...</div>
|
|
1336
1415
|
</div>
|
|
1337
1416
|
<style>@keyframes spin{to{transform:rotate(360deg)}}</style>
|
|
1338
1417
|
<script>
|
|
@@ -1345,14 +1424,39 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1345
1424
|
if (type === 'human') return '/assets/human.png';
|
|
1346
1425
|
return '/assets/agent.png';
|
|
1347
1426
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1427
|
+
// 批量 agent info 请求:攒批 50ms 后合并发送一次
|
|
1428
|
+
var _agentInfoBatchQueue=[];
|
|
1429
|
+
var _agentInfoBatchTimer=null;
|
|
1430
|
+
function _flushAgentInfoBatch(){
|
|
1431
|
+
_agentInfoBatchTimer=null;
|
|
1432
|
+
var queue=_agentInfoBatchQueue;
|
|
1433
|
+
_agentInfoBatchQueue=[];
|
|
1434
|
+
var aids=queue.map(function(q){ return q.aid; });
|
|
1435
|
+
fetch('/api/agent-info-batch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({aids:aids})})
|
|
1436
|
+
.then(function(r){ return r.json(); })
|
|
1437
|
+
.then(function(d){
|
|
1438
|
+
if(d.success&&d.data){
|
|
1439
|
+
queue.forEach(function(q){
|
|
1440
|
+
var info=d.data[q.aid]||{type:'',name:'',description:'',tags:[]};
|
|
1441
|
+
if(info.type||info.name) agentInfoCache[q.aid]=info;
|
|
1442
|
+
q.resolve(info);
|
|
1443
|
+
});
|
|
1444
|
+
} else {
|
|
1445
|
+
var empty={type:'',name:'',description:'',tags:[]};
|
|
1446
|
+
queue.forEach(function(q){ q.resolve(empty); });
|
|
1447
|
+
}
|
|
1448
|
+
})
|
|
1449
|
+
.catch(function(){
|
|
1450
|
+
var empty={type:'',name:'',description:'',tags:[]};
|
|
1451
|
+
queue.forEach(function(q){ q.resolve(empty); });
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
function fetchAgentInfo(aid){
|
|
1455
|
+
if(agentInfoCache[aid]) return Promise.resolve(agentInfoCache[aid]);
|
|
1456
|
+
return new Promise(function(resolve){
|
|
1457
|
+
_agentInfoBatchQueue.push({aid:aid,resolve:resolve});
|
|
1458
|
+
if(!_agentInfoBatchTimer) _agentInfoBatchTimer=setTimeout(_flushAgentInfoBatch,50);
|
|
1459
|
+
});
|
|
1356
1460
|
}
|
|
1357
1461
|
async function deleteSession(e, sessionId){
|
|
1358
1462
|
e.stopPropagation();
|
|
@@ -1382,7 +1486,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1382
1486
|
} catch(err){ alert('删除失败: ' + err.message); }
|
|
1383
1487
|
}
|
|
1384
1488
|
|
|
1385
|
-
function initDom(){ D.myAid=$('myAid'); D.sList=$('sessionList'); D.title=$('chatTitle'); D.msgs=$('messages'); D.input=$('messageInput'); D.sendBtn=$('sendBtn'); D.dot=$('statusDot'); D.modal=$('modal'); D.tInput=$('targetAidInput'); D.cBtn=$('connectBtn'); D.sidebar=$('sidebar'); D.aidSel=$('aidSelect'); D.aidDot=$('aidOnlineDot'); D.aidStatusToggle=$('aidStatusToggle'); D.aidStatusText=$('aidStatusText'); D.p2pPanel=$('p2pPanel'); D.groupPanel=$('groupPanel'); D.groupList=$('groupList'); D.groupInfoBar=$('groupInfoBar'); D.groupInfoText=$('groupInfoText'); D.tabP2P=$('tabP2P'); D.tabGroup=$('tabGroup'); D.encryptBanner=$('encryptBanner'); D.newMsgTip=$('newMsgTip'); }
|
|
1489
|
+
function initDom(){ D.myAid=$('myAid'); D.sList=$('sessionList'); D.title=$('chatTitle'); D.msgs=$('messages'); D.input=$('messageInput'); D.sendBtn=$('sendBtn'); D.dot=$('statusDot'); D.modal=$('modal'); D.tInput=$('targetAidInput'); D.cBtn=$('connectBtn'); D.sidebar=$('sidebar'); D.aidSel=$('aidSelect'); D.aidDot=$('aidOnlineDot'); D.aidStatusToggle=$('aidStatusToggle'); D.aidStatusText=$('aidStatusText'); D.p2pPanel=$('p2pPanel'); D.groupPanel=$('groupPanel'); D.groupList=$('groupList'); D.groupInfoBar=$('groupInfoBar'); D.groupInfoText=$('groupInfoText'); D.tabP2P=$('tabP2P'); D.tabGroup=$('tabGroup'); D.encryptBanner=$('encryptBanner'); D.newMsgTip=$('newMsgTip'); D.inputArea=$('inputArea'); D.fileList=$('fileList'); }
|
|
1386
1490
|
|
|
1387
1491
|
function isAtBottom(){ return D.msgs.scrollHeight-D.msgs.scrollTop-D.msgs.clientHeight<150; }
|
|
1388
1492
|
function scrollToBottom(){ D.msgs.scrollTop=D.msgs.scrollHeight; hideNewMsgTip(); }
|
|
@@ -1390,7 +1494,19 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1390
1494
|
function hideNewMsgTip(){ if(D.newMsgTip) D.newMsgTip.style.display='none'; }
|
|
1391
1495
|
|
|
1392
1496
|
async function init(){
|
|
1497
|
+
// 刷新页面时强制回到身份管理页面
|
|
1498
|
+
if(!sessionStorage.getItem('chatEntry')){ window.location.href='/'; return; }
|
|
1499
|
+
sessionStorage.removeItem('chatEntry');
|
|
1393
1500
|
initDom();
|
|
1501
|
+
// 配置 marked:支持换行、GFM
|
|
1502
|
+
if(typeof marked!=='undefined'&&marked.setOptions){
|
|
1503
|
+
marked.setOptions({breaks:true,gfm:true});
|
|
1504
|
+
}
|
|
1505
|
+
// 文件拖拽支持
|
|
1506
|
+
S.pendingFiles=[];
|
|
1507
|
+
D.inputArea.addEventListener('dragover',function(e){ e.preventDefault(); e.stopPropagation(); D.inputArea.classList.add('drag-over'); });
|
|
1508
|
+
D.inputArea.addEventListener('dragleave',function(e){ e.preventDefault(); e.stopPropagation(); D.inputArea.classList.remove('drag-over'); });
|
|
1509
|
+
D.inputArea.addEventListener('drop',function(e){ e.preventDefault(); e.stopPropagation(); D.inputArea.classList.remove('drag-over'); if(e.dataTransfer&&e.dataTransfer.files) addFiles(e.dataTransfer.files); });
|
|
1394
1510
|
// 监听滚动,用户滚到底部时自动隐藏新消息提示
|
|
1395
1511
|
D.msgs.addEventListener('scroll',function(){ if(isAtBottom()) hideNewMsgTip(); });
|
|
1396
1512
|
try {
|
|
@@ -1469,6 +1585,17 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1469
1585
|
D.msgs.innerHTML=''; D.title.textContent='未选择会话';
|
|
1470
1586
|
D.sList.dataset.s='';
|
|
1471
1587
|
await loadSessions();
|
|
1588
|
+
// 群组状态重置并刷新
|
|
1589
|
+
S.groups=[]; S.activeGroupId=null;
|
|
1590
|
+
_lastGroupMsgSig='';
|
|
1591
|
+
if(S.tab==='group'){
|
|
1592
|
+
D.msgs.innerHTML='';
|
|
1593
|
+
renderGroupList();
|
|
1594
|
+
await initGroupClient();
|
|
1595
|
+
await pollGroupList();
|
|
1596
|
+
} else {
|
|
1597
|
+
renderGroupList();
|
|
1598
|
+
}
|
|
1472
1599
|
} catch(e){
|
|
1473
1600
|
msg.textContent='切换失败: '+(e.message||'未知错误');
|
|
1474
1601
|
await new Promise(function(ok){setTimeout(ok,2000);});
|
|
@@ -1678,16 +1805,79 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1678
1805
|
} catch(e){}
|
|
1679
1806
|
}
|
|
1680
1807
|
|
|
1808
|
+
function autoResizeInput(){
|
|
1809
|
+
var el=D.input;
|
|
1810
|
+
el.style.height='auto';
|
|
1811
|
+
var maxH=105; // 5行 ≈ 5*21
|
|
1812
|
+
el.style.height=Math.min(el.scrollHeight,maxH)+'px';
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function addFiles(fileListObj){
|
|
1816
|
+
for(var i=0;i<fileListObj.length;i++){
|
|
1817
|
+
var f=fileListObj[i];
|
|
1818
|
+
// 跳过过大的文件(>2MB)
|
|
1819
|
+
if(f.size>2*1024*1024){ alert('文件 '+f.name+' 超过2MB,已跳过'); continue; }
|
|
1820
|
+
S.pendingFiles.push(f);
|
|
1821
|
+
}
|
|
1822
|
+
renderFileList();
|
|
1823
|
+
}
|
|
1824
|
+
function removeFile(idx){
|
|
1825
|
+
S.pendingFiles.splice(idx,1);
|
|
1826
|
+
renderFileList();
|
|
1827
|
+
}
|
|
1828
|
+
function renderFileList(){
|
|
1829
|
+
if(!S.pendingFiles.length){ D.fileList.style.display='none'; D.fileList.innerHTML=''; return; }
|
|
1830
|
+
D.fileList.style.display='flex';
|
|
1831
|
+
D.fileList.innerHTML=S.pendingFiles.map(function(f,i){
|
|
1832
|
+
return '<div class="file-item">'+
|
|
1833
|
+
'<svg class="file-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>'+
|
|
1834
|
+
'<span class="file-name" title="'+escH(f.name)+'">'+escH(f.name)+'</span>'+
|
|
1835
|
+
'<button class="file-remove" onclick="removeFile('+i+')" title="移除">'+
|
|
1836
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'+
|
|
1837
|
+
'</button></div>';
|
|
1838
|
+
}).join('');
|
|
1839
|
+
}
|
|
1840
|
+
function readFileAsText(file){
|
|
1841
|
+
return new Promise(function(resolve){
|
|
1842
|
+
var reader=new FileReader();
|
|
1843
|
+
reader.onload=function(){ resolve(reader.result); };
|
|
1844
|
+
reader.onerror=function(){ resolve('[读取失败]'); };
|
|
1845
|
+
reader.readAsText(file);
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
async function buildFileContent(){
|
|
1849
|
+
var parts=[];
|
|
1850
|
+
for(var i=0;i<S.pendingFiles.length;i++){
|
|
1851
|
+
var f=S.pendingFiles[i];
|
|
1852
|
+
var content=await readFileAsText(f);
|
|
1853
|
+
parts.push('<file name="'+f.name+'">\\n'+content+'\\n</file>');
|
|
1854
|
+
}
|
|
1855
|
+
return parts.join('\\n');
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1681
1858
|
async function sendMessage(){
|
|
1682
1859
|
var txt=D.input.value.trim();
|
|
1683
|
-
|
|
1860
|
+
var hasFiles=S.pendingFiles&&S.pendingFiles.length>0;
|
|
1861
|
+
if(!txt&&!hasFiles){ return; }
|
|
1862
|
+
// 拼接文件内容
|
|
1863
|
+
if(hasFiles){
|
|
1864
|
+
var fileContent=await buildFileContent();
|
|
1865
|
+
txt=txt?(txt+'\\n'+fileContent):fileContent;
|
|
1866
|
+
S.pendingFiles=[];
|
|
1867
|
+
renderFileList();
|
|
1868
|
+
}
|
|
1684
1869
|
// 用户主动发送消息,确保滚动到底部
|
|
1685
1870
|
hideNewMsgTip();
|
|
1871
|
+
|
|
1872
|
+
// 禁用输入框和发送按钮
|
|
1873
|
+
D.input.disabled = true;
|
|
1874
|
+
D.sendBtn.disabled = true;
|
|
1875
|
+
|
|
1686
1876
|
// 群组模式
|
|
1687
1877
|
if(S.tab==='group'){
|
|
1688
|
-
if(!S.activeGroupId){ alert('请先选择一个群组'); return; }
|
|
1878
|
+
if(!S.activeGroupId){ alert('请先选择一个群组'); D.input.disabled = false; D.sendBtn.disabled = false; return; }
|
|
1689
1879
|
try {
|
|
1690
|
-
D.input.value='';
|
|
1880
|
+
D.input.value=''; D.input.style.height='';
|
|
1691
1881
|
var r=await fetch('/api/group/send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({groupId:S.activeGroupId,message:txt,aid:S.aid})});
|
|
1692
1882
|
var d=await r.json();
|
|
1693
1883
|
if(!d.success) alert(d.error||'发送失败');
|
|
@@ -1705,18 +1895,28 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1705
1895
|
}
|
|
1706
1896
|
}
|
|
1707
1897
|
} catch(e){ alert('发送失败'); }
|
|
1898
|
+
finally {
|
|
1899
|
+
D.input.disabled = false;
|
|
1900
|
+
D.sendBtn.disabled = false;
|
|
1901
|
+
D.input.focus();
|
|
1902
|
+
}
|
|
1708
1903
|
return;
|
|
1709
1904
|
}
|
|
1710
1905
|
// P2P 模式
|
|
1711
|
-
if(!S.sid){ alert('请先选择或新建一个会话'); return; }
|
|
1712
|
-
if(S.closed){ alert('该会话已关闭,请新建会话继续通信'); return; }
|
|
1906
|
+
if(!S.sid){ alert('请先选择或新建一个会话'); D.input.disabled = false; D.sendBtn.disabled = false; return; }
|
|
1907
|
+
if(S.closed){ alert('该会话已关闭,请新建会话继续通信'); D.input.disabled = false; D.sendBtn.disabled = false; return; }
|
|
1713
1908
|
try {
|
|
1714
|
-
D.input.value='';
|
|
1909
|
+
D.input.value=''; D.input.style.height='';
|
|
1715
1910
|
var r=await fetch('/api/ws/send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:txt,sessionId:S.sid,aid:S.aid})});
|
|
1716
1911
|
var d=await r.json();
|
|
1717
1912
|
if(!d.success) alert(d.error||'发送失败');
|
|
1718
1913
|
else { await loadMessages(); scrollToBottom(); }
|
|
1719
1914
|
} catch(e){ alert('发送失败'); }
|
|
1915
|
+
finally {
|
|
1916
|
+
D.input.disabled = false;
|
|
1917
|
+
D.sendBtn.disabled = false;
|
|
1918
|
+
D.input.focus();
|
|
1919
|
+
}
|
|
1720
1920
|
}
|
|
1721
1921
|
|
|
1722
1922
|
function toggleSidebar(){
|
|
@@ -1781,7 +1981,9 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1781
1981
|
S.tab=tab;
|
|
1782
1982
|
D.tabP2P.className='tab'+(tab==='p2p'?' active':'');
|
|
1783
1983
|
D.tabGroup.className='tab'+(tab==='group'?' active':'');
|
|
1784
|
-
D.p2pPanel.style.display=tab==='p2p'?'
|
|
1984
|
+
D.p2pPanel.style.display=tab==='p2p'?'flex':'none';
|
|
1985
|
+
if(tab==='p2p') D.p2pPanel.style.flex='1';
|
|
1986
|
+
D.groupPanel.style.flex=tab==='group'?'1':'';
|
|
1785
1987
|
D.groupPanel.style.display=tab==='group'?'flex':'none';
|
|
1786
1988
|
if(tab==='group'){
|
|
1787
1989
|
D.encryptBanner.style.display='none';
|
|
@@ -1860,6 +2062,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1860
2062
|
D.title.textContent=name;
|
|
1861
2063
|
D.groupInfoBar.style.display='flex';
|
|
1862
2064
|
D.groupInfoText.textContent=name;
|
|
2065
|
+
$('groupMemberStats').textContent='';
|
|
1863
2066
|
D.input.disabled=false;
|
|
1864
2067
|
D.input.placeholder='输入群消息...';
|
|
1865
2068
|
D.input.focus();
|
|
@@ -1875,7 +2078,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1875
2078
|
gmsg.textContent='选择群组...';
|
|
1876
2079
|
await fetch('/api/group/select',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({groupId:groupId,aid:S.aid})});
|
|
1877
2080
|
} catch(e){}
|
|
1878
|
-
//
|
|
2081
|
+
// 获取群信息判断是否为创建者,同时获取成员统计
|
|
1879
2082
|
try {
|
|
1880
2083
|
gmsg.textContent='获取群信息...';
|
|
1881
2084
|
var r=await fetch('/api/group/info?groupId='+encodeURIComponent(groupId)+'&aid='+encodeURIComponent(S.aid));
|
|
@@ -1888,6 +2091,14 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1888
2091
|
} else {
|
|
1889
2092
|
$('groupCopyLinkBtn').style.display='';
|
|
1890
2093
|
}
|
|
2094
|
+
// 展示成员统计
|
|
2095
|
+
if(d.member_stats){
|
|
2096
|
+
var ms=d.member_stats;
|
|
2097
|
+
var parts=[ms.total+'人'];
|
|
2098
|
+
if(ms.human) parts.push(ms.human+'人类');
|
|
2099
|
+
if(ms.agent) parts.push(ms.agent+'Agent');
|
|
2100
|
+
$('groupMemberStats').textContent='('+parts.join(' / ')+')';
|
|
2101
|
+
}
|
|
1891
2102
|
} catch(e){
|
|
1892
2103
|
// 获取失败时默认显示复制群链接
|
|
1893
2104
|
$('groupCopyLinkBtn').style.display='';
|
|
@@ -1911,7 +2122,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1911
2122
|
var r=await fetch('/api/group/messages?groupId='+encodeURIComponent(S.activeGroupId)+'&aid='+encodeURIComponent(S.aid));
|
|
1912
2123
|
var d=await r.json();
|
|
1913
2124
|
console.log('[pollGroupMessages] response: msgCount='+(d.messages?d.messages.length:0)+' tab='+S.tab);
|
|
1914
|
-
if(S.tab==='group'&&d.messages) renderGroupMsgs(d.messages);
|
|
2125
|
+
if(S.tab==='group'&&Array.isArray(d.messages)) renderGroupMsgs(d.messages);
|
|
1915
2126
|
} catch(e){ console.error('[pollGroupMessages] error:', e); }
|
|
1916
2127
|
}
|
|
1917
2128
|
|
|
@@ -2044,6 +2255,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
2044
2255
|
var _lastGroupMsgs=[];
|
|
2045
2256
|
var _groupRuleData=null;
|
|
2046
2257
|
function renderGroupMsgs(msgs){
|
|
2258
|
+
if(!Array.isArray(msgs)) msgs=[];
|
|
2047
2259
|
// 不在群组 tab 时不渲染,防止覆盖 P2P 消息
|
|
2048
2260
|
if(S.tab!=='group') return;
|
|
2049
2261
|
if(isUserSelecting()) return;
|
|
@@ -2099,16 +2311,33 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
2099
2311
|
} else {
|
|
2100
2312
|
D.msgs.scrollTop=prevScrollTop;
|
|
2101
2313
|
}
|
|
2102
|
-
// 异步加载未缓存的 agent info
|
|
2314
|
+
// 异步加载未缓存的 agent info,加载完成后局部更新头像和名字
|
|
2103
2315
|
var unique=needFetch.filter(function(v,i,a){ return a.indexOf(v)===i; });
|
|
2104
|
-
unique.
|
|
2105
|
-
fetchAgentInfo(
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2316
|
+
if(unique.length){
|
|
2317
|
+
fetchAgentInfo(unique[0]); // 触发批量请求(攒批机制会合并)
|
|
2318
|
+
unique.forEach(function(aid){
|
|
2319
|
+
fetchAgentInfo(aid).then(function(info){
|
|
2320
|
+
if(!info||S.tab!=='group') return;
|
|
2321
|
+
// 局部更新 DOM:找到该 sender 的所有消息,更新头像和名字
|
|
2322
|
+
var avatars=D.msgs.querySelectorAll('.msg-avatar[title="'+escH(aid)+'"]');
|
|
2323
|
+
var newSrc=getAvatarSrc(info.type);
|
|
2324
|
+
var displayName=(info.name)||aid;
|
|
2325
|
+
avatars.forEach(function(img){
|
|
2326
|
+
img.src=newSrc;
|
|
2327
|
+
img.title=displayName;
|
|
2328
|
+
// 更新同一消息里的名字
|
|
2329
|
+
var msgEl=img.closest('.message');
|
|
2330
|
+
if(msgEl){
|
|
2331
|
+
var meta=msgEl.querySelector('.msg-meta');
|
|
2332
|
+
if(meta&&!msgEl.classList.contains('sent')){
|
|
2333
|
+
var timeStr=meta.textContent.split(' · ')[1]||'';
|
|
2334
|
+
meta.textContent=displayName+' · '+timeStr;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
});
|
|
2110
2339
|
});
|
|
2111
|
-
}
|
|
2340
|
+
}
|
|
2112
2341
|
}
|
|
2113
2342
|
|
|
2114
2343
|
// Group modals
|
|
@@ -2603,6 +2832,31 @@ async function handleRequest(req, res) {
|
|
|
2603
2832
|
sendJson(res, info);
|
|
2604
2833
|
return;
|
|
2605
2834
|
}
|
|
2835
|
+
// 批量获取 agent info,优先读本地缓存,未缓存的后台预热
|
|
2836
|
+
if (pathname === '/api/agent-info-batch' && method === 'POST') {
|
|
2837
|
+
try {
|
|
2838
|
+
const body = await parseBody(req);
|
|
2839
|
+
const aids = body.aids || [];
|
|
2840
|
+
const result = {};
|
|
2841
|
+
const empty = { type: '', name: '', description: '', tags: [] };
|
|
2842
|
+
for (const aid of aids) {
|
|
2843
|
+
const cached = agentInfoCache.get(aid);
|
|
2844
|
+
if (cached && Date.now() - cached.cachedAt < AGENT_INFO_CACHE_TTL) {
|
|
2845
|
+
result[aid] = { type: cached.type, name: cached.name, description: cached.description, tags: cached.tags || [] };
|
|
2846
|
+
}
|
|
2847
|
+
else {
|
|
2848
|
+
result[aid] = cached ? { type: cached.type, name: cached.name, description: cached.description, tags: cached.tags || [] } : empty;
|
|
2849
|
+
// 后台异步拉取,下次请求就有了
|
|
2850
|
+
getAgentInfo(aid).catch(() => { });
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
sendJson(res, { success: true, data: result });
|
|
2854
|
+
}
|
|
2855
|
+
catch (e) {
|
|
2856
|
+
sendJson(res, { success: false, error: e.message });
|
|
2857
|
+
}
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2606
2860
|
// 获取远程 agent.md 原始内容
|
|
2607
2861
|
if (pathname === '/api/agent-md-raw' && method === 'GET') {
|
|
2608
2862
|
const aid = parsedUrl.query.aid;
|
|
@@ -2807,6 +3061,19 @@ async function handleRequest(req, res) {
|
|
|
2807
3061
|
sendJson(res, { success: false, error: '缺少 aid' });
|
|
2808
3062
|
return;
|
|
2809
3063
|
}
|
|
3064
|
+
// 验证目标 Agent 是否存在
|
|
3065
|
+
try {
|
|
3066
|
+
const agentMdUrl = `https://${targetAid}/agent.md`;
|
|
3067
|
+
const checkRes = await fetch(agentMdUrl, { method: 'GET', signal: AbortSignal.timeout(5000) });
|
|
3068
|
+
if (!checkRes.ok) {
|
|
3069
|
+
sendJson(res, { success: false, error: '该 AGENT 不存在,添加失败' });
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
catch (_b) {
|
|
3074
|
+
sendJson(res, { success: false, error: '该 AGENT 不存在,添加失败' });
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
2810
3077
|
// 自动上线
|
|
2811
3078
|
const instance = await ensureOnline(aid);
|
|
2812
3079
|
if (!instance.agentWS) {
|
|
@@ -3102,8 +3369,33 @@ async function handleRequest(req, res) {
|
|
|
3102
3369
|
}
|
|
3103
3370
|
const instance = await ensureOnline(aid);
|
|
3104
3371
|
await ensureGroupClient(instance);
|
|
3105
|
-
const info = await
|
|
3106
|
-
|
|
3372
|
+
const [info, membersResult] = await Promise.all([
|
|
3373
|
+
instance.agentCP.groupOps.getGroupInfo(instance.groupTargetAid, groupId),
|
|
3374
|
+
instance.agentCP.groupOps.getMembers(instance.groupTargetAid, groupId).catch(() => ({ members: [] })),
|
|
3375
|
+
]);
|
|
3376
|
+
// 只用本地缓存统计成员类型,不发远程请求,不阻塞响应
|
|
3377
|
+
let humanCount = 0, agentCount = 0;
|
|
3378
|
+
const members = membersResult.members || [];
|
|
3379
|
+
for (const m of members) {
|
|
3380
|
+
const memberAid = m.agent_id || '';
|
|
3381
|
+
if (!memberAid)
|
|
3382
|
+
continue;
|
|
3383
|
+
const cached = agentInfoCache.get(memberAid);
|
|
3384
|
+
if (cached) {
|
|
3385
|
+
if (cached.type === 'human')
|
|
3386
|
+
humanCount++;
|
|
3387
|
+
else if (cached.type === 'agent')
|
|
3388
|
+
agentCount++;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
sendJson(res, Object.assign(Object.assign({ success: true }, info), { member_stats: { total: members.length, human: humanCount, agent: agentCount } }));
|
|
3392
|
+
// 后台异步预热未缓存的 agent info,下次请求就有了
|
|
3393
|
+
for (const m of members) {
|
|
3394
|
+
const memberAid = m.agent_id || '';
|
|
3395
|
+
if (memberAid && !agentInfoCache.has(memberAid)) {
|
|
3396
|
+
getAgentInfo(memberAid).catch(() => { });
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3107
3399
|
}
|
|
3108
3400
|
catch (e) {
|
|
3109
3401
|
sendJson(res, { success: false, error: e.message });
|
|
@@ -3136,6 +3428,7 @@ async function handleRequest(req, res) {
|
|
|
3136
3428
|
sendJson(res, Object.assign({ success: true }, result));
|
|
3137
3429
|
}
|
|
3138
3430
|
catch (e) {
|
|
3431
|
+
utils_1.logger.error(`[API] /api/group/send FAILED: error=${e.message}`);
|
|
3139
3432
|
sendJson(res, { success: false, error: e.message });
|
|
3140
3433
|
}
|
|
3141
3434
|
return;
|
|
@@ -3156,7 +3449,7 @@ async function handleRequest(req, res) {
|
|
|
3156
3449
|
await ensureGroupClient(instance);
|
|
3157
3450
|
// 只读本地缓存,不再每次请求都去服务端拉取
|
|
3158
3451
|
// 新消息通过 WebSocket 推送实时到达并由 SDK 自动存储
|
|
3159
|
-
const messages = instance.agentCP.getLocalGroupMessages(groupId);
|
|
3452
|
+
const messages = instance.agentCP.getLocalGroupMessages(groupId) || [];
|
|
3160
3453
|
utils_1.logger.log(`[API] /api/group/messages: aid=${aid} group=${groupId} localMsgCount=${messages.length} lastMsgId=${messages.length > 0 ? messages[messages.length - 1].msg_id : 'none'} storeExists=${!!instance.agentCP.groupMessageStore}`);
|
|
3161
3454
|
sendJson(res, { success: true, messages });
|
|
3162
3455
|
}
|
|
@@ -3208,7 +3501,7 @@ async function handleRequest(req, res) {
|
|
|
3208
3501
|
let groupName = groupId;
|
|
3209
3502
|
try {
|
|
3210
3503
|
const info = await instance.agentCP.groupOps.getGroupInfo(targetAid, groupId);
|
|
3211
|
-
groupName = info.name || groupId;
|
|
3504
|
+
groupName = (info && info.name) || groupId;
|
|
3212
3505
|
}
|
|
3213
3506
|
catch (_) { }
|
|
3214
3507
|
instance.agentCP.addGroupToStore(groupId, groupName);
|
|
@@ -3319,11 +3612,11 @@ async function handleRequest(req, res) {
|
|
|
3319
3612
|
const result = await ops.listMyGroups(target);
|
|
3320
3613
|
// 尝试获取每个群的详细信息(名称等)
|
|
3321
3614
|
const groups = [];
|
|
3322
|
-
for (const m of result.groups) {
|
|
3615
|
+
for (const m of (result.groups || [])) {
|
|
3323
3616
|
let name = m.group_id;
|
|
3324
3617
|
try {
|
|
3325
3618
|
const info = await ops.getGroupInfo(target, m.group_id);
|
|
3326
|
-
name = info.name || m.group_id;
|
|
3619
|
+
name = (info && info.name) || m.group_id;
|
|
3327
3620
|
}
|
|
3328
3621
|
catch (_) { }
|
|
3329
3622
|
groups.push(Object.assign(Object.assign({}, m), { name }));
|