acp-ts 1.2.4 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentcp.js +10 -7
- 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 +211 -63
- package/package.json +1 -1
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
|
}
|
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
|
@@ -230,7 +230,7 @@ async function doEnsureOnline(aid) {
|
|
|
230
230
|
try {
|
|
231
231
|
const parsed = JSON.parse(msgContent);
|
|
232
232
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
233
|
-
content = parsed.map((item) => item.content || '').join('');
|
|
233
|
+
content = parsed.map((item) => (item && item.content) || '').join('');
|
|
234
234
|
}
|
|
235
235
|
else if (parsed.content) {
|
|
236
236
|
content = parsed.content;
|
|
@@ -360,7 +360,7 @@ async function ensureGroupClient(instance) {
|
|
|
360
360
|
let groupName = groupId;
|
|
361
361
|
try {
|
|
362
362
|
const info = await instance.agentCP.groupOps.getGroupInfo(instance.groupTargetAid, groupId);
|
|
363
|
-
groupName = info.name || groupId;
|
|
363
|
+
groupName = (info && info.name) || groupId;
|
|
364
364
|
}
|
|
365
365
|
catch (_) { }
|
|
366
366
|
instance.agentCP.addGroupToStore(groupId, groupName);
|
|
@@ -386,7 +386,8 @@ async function ensureGroupClient(instance) {
|
|
|
386
386
|
broadcastToBrowser({ type: 'join_request', group_id: groupId, agent_id: agentId, message });
|
|
387
387
|
},
|
|
388
388
|
onGroupMessageBatch(groupId, batch) {
|
|
389
|
-
|
|
389
|
+
const batchMessages = batch.messages || [];
|
|
390
|
+
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
391
|
// 存储 + ACK(统一由 agentcp 处理),注意 processAndAckBatch 是 async
|
|
391
392
|
instance.agentCP.processAndAckBatch(groupId, batch).then((sorted) => {
|
|
392
393
|
var _a, _b;
|
|
@@ -480,7 +481,7 @@ async function getAidStatusList() {
|
|
|
480
481
|
const agentInfoCache = new Map();
|
|
481
482
|
const AGENT_INFO_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
482
483
|
function getAgentInfoCachePath() {
|
|
483
|
-
const dir = globalDataDir ||
|
|
484
|
+
const dir = globalDataDir || datamanager_1.DEFAULT_ACP_DIR;
|
|
484
485
|
return path.join(dir, 'AIDs', '.agent-info-cache.json');
|
|
485
486
|
}
|
|
486
487
|
function loadAgentInfoCacheFromDisk() {
|
|
@@ -579,7 +580,7 @@ async function getAgentInfo(aid) {
|
|
|
579
580
|
}
|
|
580
581
|
// 每个 AID 的自定义 agent.md 选项 (昵称、描述)
|
|
581
582
|
function getAidMdOptionsPath() {
|
|
582
|
-
const dir = globalDataDir ||
|
|
583
|
+
const dir = globalDataDir || datamanager_1.DEFAULT_ACP_DIR;
|
|
583
584
|
return path.join(dir, 'AIDs', '.aid-md-options.json');
|
|
584
585
|
}
|
|
585
586
|
function loadAidMdOptions() {
|
|
@@ -614,7 +615,7 @@ function getMessageStoreForAid(aid) {
|
|
|
614
615
|
if (!store) {
|
|
615
616
|
store = new messagestore_1.MessageStore({
|
|
616
617
|
persistMessages: true,
|
|
617
|
-
basePath: globalDataDir ||
|
|
618
|
+
basePath: globalDataDir || datamanager_1.DEFAULT_ACP_DIR,
|
|
618
619
|
});
|
|
619
620
|
messageStores.set(aid, store);
|
|
620
621
|
}
|
|
@@ -638,6 +639,10 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
638
639
|
<title>ACP 身份管理</title>
|
|
639
640
|
<style>
|
|
640
641
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
642
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
643
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
644
|
+
::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 3px; }
|
|
645
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.25); }
|
|
641
646
|
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
647
|
.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
648
|
.page-header { background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%); padding: 28px 32px 22px; color: white; text-align: center; }
|
|
@@ -674,6 +679,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
674
679
|
.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
680
|
.create-section .extra-fields input:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,0.1); }
|
|
676
681
|
.btn { display: block; width: 100%; padding: 11px; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; }
|
|
682
|
+
.btn:active { transform: scale(0.98); }
|
|
677
683
|
.btn-primary { background: linear-gradient(135deg, #2563eb, #1d4ed8); color: white; }
|
|
678
684
|
.btn-primary:hover { background: linear-gradient(135deg, #1d4ed8, #1e40af); box-shadow: 0 2px 8px rgba(37,99,235,0.3); }
|
|
679
685
|
.btn-sm { display: inline-block; width: auto; padding: 6px 14px; font-size: 13px; border-radius: 6px; }
|
|
@@ -684,10 +690,10 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
684
690
|
.btn-outline { background: white; color: #2563eb; border: 1px solid #2563eb; }
|
|
685
691
|
.btn-outline:hover { background: #eff6ff; }
|
|
686
692
|
.btn-outline.active { background: #2563eb; color: white; }
|
|
687
|
-
.btn:disabled { background: #d1d5db; cursor: not-allowed; border-color: #d1d5db; color: #fff; }
|
|
693
|
+
.btn:disabled { background: #d1d5db; cursor: not-allowed; border-color: #d1d5db; color: #fff; transform: none; }
|
|
688
694
|
.aid-list { margin-bottom: 24px; }
|
|
689
695
|
.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
|
|
696
|
+
.aid-card:hover { border-color: #93c5fd; box-shadow: 0 4px 12px rgba(37,99,235,0.08); transform: translateY(-1px); }
|
|
691
697
|
.aid-card.current { border-color: #2563eb; background: #eff6ff; }
|
|
692
698
|
.aid-card-left { flex: 1; min-width: 0; }
|
|
693
699
|
.aid-card-right { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; justify-content: center; }
|
|
@@ -703,9 +709,9 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
703
709
|
.badge-info { background: #dbeafe; color: #1e40af; }
|
|
704
710
|
.badge-current { background: #2563eb; color: white; }
|
|
705
711
|
.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 {
|
|
712
|
+
.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); }
|
|
713
|
+
.status.success { opacity: 1; visibility: visible; transform: translate(-50%, 0); background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
|
|
714
|
+
.status.error { opacity: 1; visibility: visible; transform: translate(-50%, 0); background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
|
|
709
715
|
@media (max-width: 480px) {
|
|
710
716
|
body { padding: 16px 8px; }
|
|
711
717
|
.page-header { padding: 22px 18px 18px; }
|
|
@@ -892,11 +898,13 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
892
898
|
});
|
|
893
899
|
}
|
|
894
900
|
|
|
901
|
+
let statusTimeout = null;
|
|
895
902
|
function showStatus(msg, type) {
|
|
896
903
|
var el = document.getElementById('status');
|
|
897
904
|
el.textContent = msg;
|
|
898
905
|
el.className = 'status ' + type;
|
|
899
|
-
|
|
906
|
+
if (statusTimeout) clearTimeout(statusTimeout);
|
|
907
|
+
statusTimeout = setTimeout(function() { el.className = 'status'; }, 3000);
|
|
900
908
|
}
|
|
901
909
|
|
|
902
910
|
function escapeHtml(text) {
|
|
@@ -924,6 +932,10 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
924
932
|
<style>
|
|
925
933
|
: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
934
|
* { box-sizing:border-box; margin:0; padding:0; }
|
|
935
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
936
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
937
|
+
::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 3px; }
|
|
938
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.25); }
|
|
927
939
|
body { font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; background:var(--bg); height:100vh; overflow:hidden; color:var(--t1); }
|
|
928
940
|
#app { display:flex; height:100%; }
|
|
929
941
|
|
|
@@ -933,8 +945,9 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
933
945
|
.sidebar-header { padding:12px 14px; border-bottom:1px solid var(--border); display:flex; flex-direction:column; gap:12px; flex-shrink:0; }
|
|
934
946
|
.header-top { display:flex; justify-content:space-between; align-items:center; width:100%; }
|
|
935
947
|
.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; }
|
|
948
|
+
.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
949
|
.new-chat-btn:hover { background:var(--primary-h); }
|
|
950
|
+
.new-chat-btn:active { transform:scale(0.96); }
|
|
938
951
|
.session-list { flex:1; overflow-y:auto; }
|
|
939
952
|
|
|
940
953
|
/* AID Group */
|
|
@@ -947,12 +960,12 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
947
960
|
.aid-group-arrow { font-size:10px; color:var(--primary); transition:transform 0.2s; flex-shrink:0; }
|
|
948
961
|
.aid-group-arrow.open { transform:rotate(90deg); }
|
|
949
962
|
.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; }
|
|
963
|
+
.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
964
|
.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; }
|
|
965
|
+
.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
966
|
.aid-group-header:hover .aid-group-del { display:block; }
|
|
954
967
|
.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; }
|
|
968
|
+
.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
969
|
.session-item:hover .session-del { display:block; }
|
|
957
970
|
.session-del:hover { color:#dc3545; }
|
|
958
971
|
.aid-group-sessions { display:none; background:#fafbfc; }
|
|
@@ -961,11 +974,11 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
961
974
|
.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
975
|
|
|
963
976
|
.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; }
|
|
977
|
+
.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
978
|
.session-item:hover { background:#f0f5ff; }
|
|
966
979
|
.session-item.active { background:#eff6ff; border-left:3px solid var(--primary); padding-left:29px; }
|
|
967
980
|
.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; }
|
|
981
|
+
.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
982
|
.session-item.active .session-peer { background:#dbeafe; border-color:#bfdbfe; color:#1e40af; }
|
|
970
983
|
.session-meta { font-size:10px; color:var(--t2); margin-top:4px; display:flex; align-items:center; gap:6px; padding-left:10px; }
|
|
971
984
|
.tag { font-size:9px; padding:1px 5px; border-radius:3px; color:#fff; font-weight:600; letter-spacing:0.3px; }
|
|
@@ -976,16 +989,17 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
976
989
|
.chat-area { flex:1; display:flex; flex-direction:column; background:var(--chat-bg); min-width:0; }
|
|
977
990
|
.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
991
|
.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; }
|
|
992
|
+
.toggle-sidebar-btn { background:none; border:none; cursor:pointer; color:var(--t2); padding:4px; display:flex; transition:all 0.15s; }
|
|
980
993
|
.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); }
|
|
994
|
+
.status-dot { width:8px; height:8px; border-radius:50%; background:#ccc; flex-shrink:0; transition:all 0.3s; }
|
|
995
|
+
.status-dot.connected { background:var(--ok); box-shadow:0 0 0 2px rgba(16,185,129,0.2); }
|
|
983
996
|
.status-dot.connecting { background:#fbbf24; }
|
|
984
997
|
.chat-title { font-size:15px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
985
998
|
|
|
986
999
|
.aid-select-wrap { display:flex; align-items:center; gap:10px; flex-shrink:0; }
|
|
987
1000
|
.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
1001
|
.manage-btn:hover { background:#f8fafc; color:var(--primary); border-color:var(--primary); }
|
|
1002
|
+
.manage-btn:active { transform:scale(0.96); }
|
|
989
1003
|
.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
1004
|
.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
1005
|
.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 +1008,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
994
1008
|
.status-indicator.online { background:var(--ok); box-shadow:0 0 0 2px rgba(16,185,129,0.2); }
|
|
995
1009
|
.status-indicator.offline { background:#cbd5e1; }
|
|
996
1010
|
|
|
997
|
-
.collapse-btn { background:none; border:none; cursor:pointer; color:var(--t2); padding:6px; display:flex; align-items:center; flex-shrink:0; }
|
|
1011
|
+
.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
1012
|
.collapse-btn:hover { color:var(--t1); }
|
|
999
1013
|
|
|
1000
1014
|
.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 +1018,34 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1004
1018
|
.message { display:flex; flex-direction:column; max-width:80%; }
|
|
1005
1019
|
.message.sent { align-self:flex-end; align-items:flex-end; }
|
|
1006
1020
|
.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:
|
|
1021
|
+
.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); }
|
|
1022
|
+
.message.sent .bubble { background:var(--sent); color:#fff; border-bottom-right-radius:4px; }
|
|
1023
|
+
.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
1024
|
.msg-meta { font-size:10px; color:var(--t2); margin-bottom:3px; padding:0 4px; }
|
|
1011
1025
|
|
|
1012
|
-
.input-area { padding:12px 16px; background:#fff; border-top:1px solid var(--border); display:flex;
|
|
1013
|
-
.input-area
|
|
1014
|
-
.input-
|
|
1015
|
-
.
|
|
1026
|
+
.input-area { padding:12px 16px; background:#fff; border-top:1px solid var(--border); display:flex; flex-direction:column; gap:8px; flex-shrink:0; }
|
|
1027
|
+
.input-area.drag-over { background:#eff6ff; border-top-color:var(--primary); }
|
|
1028
|
+
.input-row { display:flex; align-items:flex-end; gap:10px; }
|
|
1029
|
+
.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; }
|
|
1030
|
+
.input-area textarea:focus { outline:none; border-color:var(--primary); background:#fff; box-shadow:0 0 0 3px rgba(37,99,235,0.1); }
|
|
1031
|
+
.file-list { display:flex; flex-wrap:wrap; gap:6px; padding:4px 0; }
|
|
1032
|
+
.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; }
|
|
1033
|
+
.file-item .file-icon { flex-shrink:0; width:16px; height:16px; color:var(--primary); }
|
|
1034
|
+
.file-item .file-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1035
|
+
.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; }
|
|
1036
|
+
.file-item .file-remove:hover { color:#e53e3e; background:rgba(229,62,62,0.1); }
|
|
1037
|
+
.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
1038
|
.send-btn:hover { background:var(--primary-h); }
|
|
1017
|
-
.send-btn:
|
|
1039
|
+
.send-btn:active { transform:scale(0.94); }
|
|
1040
|
+
.send-btn:disabled { background:#ccc; cursor:not-allowed; transform:none; }
|
|
1018
1041
|
|
|
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); }
|
|
1042
|
+
.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); }
|
|
1043
|
+
.modal-overlay.show { opacity:1; visibility:visible; }
|
|
1044
|
+
.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); }
|
|
1045
|
+
.modal-overlay.show .modal { transform:scale(1) translateY(0); }
|
|
1022
1046
|
.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); }
|
|
1047
|
+
.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; }
|
|
1048
|
+
.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
1049
|
.modal input[type="radio"] { width:auto; margin:0; }
|
|
1026
1050
|
.group-type-card { flex:1; padding:12px; border:2px solid var(--border); border-radius:10px; cursor:pointer; transition:all 0.2s; background:#fafafa; }
|
|
1027
1051
|
.group-type-card:hover { border-color:#b0b0b0; background:#f5f5f5; }
|
|
@@ -1030,10 +1054,11 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1030
1054
|
.duty-rule-card:hover { border-color:#b0b0b0; background:#f5f5f5; }
|
|
1031
1055
|
.duty-rule-card.selected { border-color:var(--primary); background:rgba(0,122,255,0.06); }
|
|
1032
1056
|
.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; }
|
|
1057
|
+
.mbtn { padding:8px 16px; border-radius:6px; font-size:13px; cursor:pointer; border:none; transition:all 0.15s; }
|
|
1058
|
+
.mbtn:active { transform:scale(0.96); }
|
|
1034
1059
|
.mbtn-cancel { background:#f3f4f6; color:var(--t1); }
|
|
1035
1060
|
.mbtn-ok { background:var(--primary); color:#fff; }
|
|
1036
|
-
.mbtn-ok:disabled { background:#ccc; }
|
|
1061
|
+
.mbtn-ok:disabled { background:#ccc; transform:none; }
|
|
1037
1062
|
|
|
1038
1063
|
.bubble p { margin-bottom:0.4em; } .bubble p:last-child { margin-bottom:0; }
|
|
1039
1064
|
.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 +1074,16 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1049
1074
|
.bubble code { background:rgba(0,0,0,0.1); padding:2px 4px; border-radius:3px; font-family:monospace; font-size:0.9em; }
|
|
1050
1075
|
.bubble pre { background:#2d2d2d; color:#fff; padding:12px; border-radius:6px; overflow-x:auto; margin:8px 0; }
|
|
1051
1076
|
.bubble pre code { background:transparent; padding:0; color:inherit; border-radius:0; }
|
|
1077
|
+
.bubble table { border-collapse:collapse; width:100%; margin:8px 0; font-size:0.9em; }
|
|
1078
|
+
.bubble th, .bubble td { border:1px solid rgba(0,0,0,0.15); padding:6px 10px; text-align:left; }
|
|
1079
|
+
.bubble th { background:rgba(0,0,0,0.05); font-weight:600; }
|
|
1080
|
+
.bubble hr { border:none; border-top:1px solid rgba(0,0,0,0.1); margin:0.8em 0; }
|
|
1081
|
+
.message.sent .bubble blockquote { color:rgba(255,255,255,0.8); border-left-color:rgba(255,255,255,0.4); }
|
|
1082
|
+
.message.sent .bubble code { background:rgba(255,255,255,0.2); }
|
|
1083
|
+
.message.sent .bubble th, .message.sent .bubble td { border-color:rgba(255,255,255,0.3); }
|
|
1084
|
+
.message.sent .bubble th { background:rgba(255,255,255,0.1); }
|
|
1085
|
+
.message.sent .bubble hr { border-top-color:rgba(255,255,255,0.3); }
|
|
1086
|
+
.message.sent .bubble h1, .message.sent .bubble h2 { border-bottom-color:rgba(255,255,255,0.2); }
|
|
1052
1087
|
.bubble-wrap { position:relative; }
|
|
1053
1088
|
.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
1089
|
.bubble-wrap:hover .copy-msg-btn { opacity:1; pointer-events:auto; }
|
|
@@ -1085,14 +1120,15 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1085
1120
|
.group-item.active { background:#eff6ff; border-left:3px solid var(--primary); padding-left:11px; }
|
|
1086
1121
|
.group-item-name { font-size:13px; font-weight:600; color:var(--t1); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1087
1122
|
.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; }
|
|
1123
|
+
.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
1124
|
.group-item:hover .group-item-del { display:block; }
|
|
1090
1125
|
.group-item-del:hover { color:#dc3545; }
|
|
1091
1126
|
.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; }
|
|
1127
|
+
.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
1128
|
.group-actions .gbtn:hover { background:#f1f5f9; border-color:var(--primary); color:var(--primary); }
|
|
1129
|
+
.group-actions .gbtn:active { transform:scale(0.96); }
|
|
1094
1130
|
.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; }
|
|
1131
|
+
.group-info-bar .copy-link { cursor:pointer; text-decoration:underline; transition:all 0.15s; }
|
|
1096
1132
|
.group-info-bar .copy-link:hover { color:#0284c7; }
|
|
1097
1133
|
</style>
|
|
1098
1134
|
<!-- CHATHTML_STYLE_END -->
|
|
@@ -1113,12 +1149,12 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1113
1149
|
</div>
|
|
1114
1150
|
</div>
|
|
1115
1151
|
<!-- P2P panel -->
|
|
1116
|
-
<div id="p2pPanel">
|
|
1152
|
+
<div id="p2pPanel" style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
|
|
1117
1153
|
<div style="padding:8px 14px;flex-shrink:0;"><button class="new-chat-btn" onclick="showModal()">+ 连接龙虾</button></div>
|
|
1118
1154
|
<div class="session-list" id="sessionList"></div>
|
|
1119
1155
|
</div>
|
|
1120
1156
|
<!-- Group panel -->
|
|
1121
|
-
<div id="groupPanel" style="display:none;flex
|
|
1157
|
+
<div id="groupPanel" style="display:none;flex-direction:column;overflow:hidden;">
|
|
1122
1158
|
<div class="group-actions">
|
|
1123
1159
|
<div class="gbtn" onclick="showCreateGroupModal()">创建群组</div>
|
|
1124
1160
|
<div class="gbtn" onclick="showJoinGroupModal()">加入群组</div>
|
|
@@ -1173,11 +1209,14 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1173
1209
|
</div>
|
|
1174
1210
|
<div class="new-msg-tip" id="newMsgTip" onclick="scrollToBottom()">↓ 有新消息</div>
|
|
1175
1211
|
</div>
|
|
1176
|
-
<div class="input-area">
|
|
1177
|
-
<
|
|
1212
|
+
<div class="input-area" id="inputArea">
|
|
1213
|
+
<div class="file-list" id="fileList" style="display:none;"></div>
|
|
1214
|
+
<div class="input-row">
|
|
1215
|
+
<textarea id="messageInput" rows="3" placeholder="输入消息... 可拖入文件" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMessage();}" oninput="autoResizeInput()"></textarea>
|
|
1178
1216
|
<button class="send-btn" id="sendBtn" onclick="sendMessage()">
|
|
1179
1217
|
<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
1218
|
</button>
|
|
1219
|
+
</div>
|
|
1181
1220
|
</div>
|
|
1182
1221
|
</div>
|
|
1183
1222
|
</div>
|
|
@@ -1326,13 +1365,13 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1326
1365
|
</div>
|
|
1327
1366
|
</div>
|
|
1328
1367
|
</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
|
|
1368
|
+
<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;">
|
|
1369
|
+
<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>
|
|
1370
|
+
<div id="switchAidMsg" style="color:var(--t1);font-size:15px;font-weight:500;">切换身份中...</div>
|
|
1332
1371
|
</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
|
|
1372
|
+
<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);">
|
|
1373
|
+
<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>
|
|
1374
|
+
<div id="switchGroupMsg" style="color:var(--t1);font-size:14px;font-weight:500;">加载群组中...</div>
|
|
1336
1375
|
</div>
|
|
1337
1376
|
<style>@keyframes spin{to{transform:rotate(360deg)}}</style>
|
|
1338
1377
|
<script>
|
|
@@ -1382,7 +1421,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1382
1421
|
} catch(err){ alert('删除失败: ' + err.message); }
|
|
1383
1422
|
}
|
|
1384
1423
|
|
|
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'); }
|
|
1424
|
+
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
1425
|
|
|
1387
1426
|
function isAtBottom(){ return D.msgs.scrollHeight-D.msgs.scrollTop-D.msgs.clientHeight<150; }
|
|
1388
1427
|
function scrollToBottom(){ D.msgs.scrollTop=D.msgs.scrollHeight; hideNewMsgTip(); }
|
|
@@ -1391,6 +1430,15 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1391
1430
|
|
|
1392
1431
|
async function init(){
|
|
1393
1432
|
initDom();
|
|
1433
|
+
// 配置 marked:支持换行、GFM
|
|
1434
|
+
if(typeof marked!=='undefined'&&marked.setOptions){
|
|
1435
|
+
marked.setOptions({breaks:true,gfm:true});
|
|
1436
|
+
}
|
|
1437
|
+
// 文件拖拽支持
|
|
1438
|
+
S.pendingFiles=[];
|
|
1439
|
+
D.inputArea.addEventListener('dragover',function(e){ e.preventDefault(); e.stopPropagation(); D.inputArea.classList.add('drag-over'); });
|
|
1440
|
+
D.inputArea.addEventListener('dragleave',function(e){ e.preventDefault(); e.stopPropagation(); D.inputArea.classList.remove('drag-over'); });
|
|
1441
|
+
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
1442
|
// 监听滚动,用户滚到底部时自动隐藏新消息提示
|
|
1395
1443
|
D.msgs.addEventListener('scroll',function(){ if(isAtBottom()) hideNewMsgTip(); });
|
|
1396
1444
|
try {
|
|
@@ -1469,6 +1517,17 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1469
1517
|
D.msgs.innerHTML=''; D.title.textContent='未选择会话';
|
|
1470
1518
|
D.sList.dataset.s='';
|
|
1471
1519
|
await loadSessions();
|
|
1520
|
+
// 群组状态重置并刷新
|
|
1521
|
+
S.groups=[]; S.activeGroupId=null;
|
|
1522
|
+
_lastGroupMsgSig='';
|
|
1523
|
+
if(S.tab==='group'){
|
|
1524
|
+
D.msgs.innerHTML='';
|
|
1525
|
+
renderGroupList();
|
|
1526
|
+
await initGroupClient();
|
|
1527
|
+
await pollGroupList();
|
|
1528
|
+
} else {
|
|
1529
|
+
renderGroupList();
|
|
1530
|
+
}
|
|
1472
1531
|
} catch(e){
|
|
1473
1532
|
msg.textContent='切换失败: '+(e.message||'未知错误');
|
|
1474
1533
|
await new Promise(function(ok){setTimeout(ok,2000);});
|
|
@@ -1678,16 +1737,79 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1678
1737
|
} catch(e){}
|
|
1679
1738
|
}
|
|
1680
1739
|
|
|
1740
|
+
function autoResizeInput(){
|
|
1741
|
+
var el=D.input;
|
|
1742
|
+
el.style.height='auto';
|
|
1743
|
+
var maxH=105; // 5行 ≈ 5*21
|
|
1744
|
+
el.style.height=Math.min(el.scrollHeight,maxH)+'px';
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
function addFiles(fileListObj){
|
|
1748
|
+
for(var i=0;i<fileListObj.length;i++){
|
|
1749
|
+
var f=fileListObj[i];
|
|
1750
|
+
// 跳过过大的文件(>2MB)
|
|
1751
|
+
if(f.size>2*1024*1024){ alert('文件 '+f.name+' 超过2MB,已跳过'); continue; }
|
|
1752
|
+
S.pendingFiles.push(f);
|
|
1753
|
+
}
|
|
1754
|
+
renderFileList();
|
|
1755
|
+
}
|
|
1756
|
+
function removeFile(idx){
|
|
1757
|
+
S.pendingFiles.splice(idx,1);
|
|
1758
|
+
renderFileList();
|
|
1759
|
+
}
|
|
1760
|
+
function renderFileList(){
|
|
1761
|
+
if(!S.pendingFiles.length){ D.fileList.style.display='none'; D.fileList.innerHTML=''; return; }
|
|
1762
|
+
D.fileList.style.display='flex';
|
|
1763
|
+
D.fileList.innerHTML=S.pendingFiles.map(function(f,i){
|
|
1764
|
+
return '<div class="file-item">'+
|
|
1765
|
+
'<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>'+
|
|
1766
|
+
'<span class="file-name" title="'+escH(f.name)+'">'+escH(f.name)+'</span>'+
|
|
1767
|
+
'<button class="file-remove" onclick="removeFile('+i+')" title="移除">'+
|
|
1768
|
+
'<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>'+
|
|
1769
|
+
'</button></div>';
|
|
1770
|
+
}).join('');
|
|
1771
|
+
}
|
|
1772
|
+
function readFileAsText(file){
|
|
1773
|
+
return new Promise(function(resolve){
|
|
1774
|
+
var reader=new FileReader();
|
|
1775
|
+
reader.onload=function(){ resolve(reader.result); };
|
|
1776
|
+
reader.onerror=function(){ resolve('[读取失败]'); };
|
|
1777
|
+
reader.readAsText(file);
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
async function buildFileContent(){
|
|
1781
|
+
var parts=[];
|
|
1782
|
+
for(var i=0;i<S.pendingFiles.length;i++){
|
|
1783
|
+
var f=S.pendingFiles[i];
|
|
1784
|
+
var content=await readFileAsText(f);
|
|
1785
|
+
parts.push('<file name="'+f.name+'">\\n'+content+'\\n</file>');
|
|
1786
|
+
}
|
|
1787
|
+
return parts.join('\\n');
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1681
1790
|
async function sendMessage(){
|
|
1682
1791
|
var txt=D.input.value.trim();
|
|
1683
|
-
|
|
1792
|
+
var hasFiles=S.pendingFiles&&S.pendingFiles.length>0;
|
|
1793
|
+
if(!txt&&!hasFiles){ return; }
|
|
1794
|
+
// 拼接文件内容
|
|
1795
|
+
if(hasFiles){
|
|
1796
|
+
var fileContent=await buildFileContent();
|
|
1797
|
+
txt=txt?(txt+'\\n'+fileContent):fileContent;
|
|
1798
|
+
S.pendingFiles=[];
|
|
1799
|
+
renderFileList();
|
|
1800
|
+
}
|
|
1684
1801
|
// 用户主动发送消息,确保滚动到底部
|
|
1685
1802
|
hideNewMsgTip();
|
|
1803
|
+
|
|
1804
|
+
// 禁用输入框和发送按钮
|
|
1805
|
+
D.input.disabled = true;
|
|
1806
|
+
D.sendBtn.disabled = true;
|
|
1807
|
+
|
|
1686
1808
|
// 群组模式
|
|
1687
1809
|
if(S.tab==='group'){
|
|
1688
|
-
if(!S.activeGroupId){ alert('请先选择一个群组'); return; }
|
|
1810
|
+
if(!S.activeGroupId){ alert('请先选择一个群组'); D.input.disabled = false; D.sendBtn.disabled = false; return; }
|
|
1689
1811
|
try {
|
|
1690
|
-
D.input.value='';
|
|
1812
|
+
D.input.value=''; D.input.style.height='';
|
|
1691
1813
|
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
1814
|
var d=await r.json();
|
|
1693
1815
|
if(!d.success) alert(d.error||'发送失败');
|
|
@@ -1705,18 +1827,28 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1705
1827
|
}
|
|
1706
1828
|
}
|
|
1707
1829
|
} catch(e){ alert('发送失败'); }
|
|
1830
|
+
finally {
|
|
1831
|
+
D.input.disabled = false;
|
|
1832
|
+
D.sendBtn.disabled = false;
|
|
1833
|
+
D.input.focus();
|
|
1834
|
+
}
|
|
1708
1835
|
return;
|
|
1709
1836
|
}
|
|
1710
1837
|
// P2P 模式
|
|
1711
|
-
if(!S.sid){ alert('请先选择或新建一个会话'); return; }
|
|
1712
|
-
if(S.closed){ alert('该会话已关闭,请新建会话继续通信'); return; }
|
|
1838
|
+
if(!S.sid){ alert('请先选择或新建一个会话'); D.input.disabled = false; D.sendBtn.disabled = false; return; }
|
|
1839
|
+
if(S.closed){ alert('该会话已关闭,请新建会话继续通信'); D.input.disabled = false; D.sendBtn.disabled = false; return; }
|
|
1713
1840
|
try {
|
|
1714
|
-
D.input.value='';
|
|
1841
|
+
D.input.value=''; D.input.style.height='';
|
|
1715
1842
|
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
1843
|
var d=await r.json();
|
|
1717
1844
|
if(!d.success) alert(d.error||'发送失败');
|
|
1718
1845
|
else { await loadMessages(); scrollToBottom(); }
|
|
1719
1846
|
} catch(e){ alert('发送失败'); }
|
|
1847
|
+
finally {
|
|
1848
|
+
D.input.disabled = false;
|
|
1849
|
+
D.sendBtn.disabled = false;
|
|
1850
|
+
D.input.focus();
|
|
1851
|
+
}
|
|
1720
1852
|
}
|
|
1721
1853
|
|
|
1722
1854
|
function toggleSidebar(){
|
|
@@ -1781,7 +1913,9 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1781
1913
|
S.tab=tab;
|
|
1782
1914
|
D.tabP2P.className='tab'+(tab==='p2p'?' active':'');
|
|
1783
1915
|
D.tabGroup.className='tab'+(tab==='group'?' active':'');
|
|
1784
|
-
D.p2pPanel.style.display=tab==='p2p'?'
|
|
1916
|
+
D.p2pPanel.style.display=tab==='p2p'?'flex':'none';
|
|
1917
|
+
if(tab==='p2p') D.p2pPanel.style.flex='1';
|
|
1918
|
+
D.groupPanel.style.flex=tab==='group'?'1':'';
|
|
1785
1919
|
D.groupPanel.style.display=tab==='group'?'flex':'none';
|
|
1786
1920
|
if(tab==='group'){
|
|
1787
1921
|
D.encryptBanner.style.display='none';
|
|
@@ -1911,7 +2045,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1911
2045
|
var r=await fetch('/api/group/messages?groupId='+encodeURIComponent(S.activeGroupId)+'&aid='+encodeURIComponent(S.aid));
|
|
1912
2046
|
var d=await r.json();
|
|
1913
2047
|
console.log('[pollGroupMessages] response: msgCount='+(d.messages?d.messages.length:0)+' tab='+S.tab);
|
|
1914
|
-
if(S.tab==='group'&&d.messages) renderGroupMsgs(d.messages);
|
|
2048
|
+
if(S.tab==='group'&&Array.isArray(d.messages)) renderGroupMsgs(d.messages);
|
|
1915
2049
|
} catch(e){ console.error('[pollGroupMessages] error:', e); }
|
|
1916
2050
|
}
|
|
1917
2051
|
|
|
@@ -2044,6 +2178,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
2044
2178
|
var _lastGroupMsgs=[];
|
|
2045
2179
|
var _groupRuleData=null;
|
|
2046
2180
|
function renderGroupMsgs(msgs){
|
|
2181
|
+
if(!Array.isArray(msgs)) msgs=[];
|
|
2047
2182
|
// 不在群组 tab 时不渲染,防止覆盖 P2P 消息
|
|
2048
2183
|
if(S.tab!=='group') return;
|
|
2049
2184
|
if(isUserSelecting()) return;
|
|
@@ -2807,6 +2942,19 @@ async function handleRequest(req, res) {
|
|
|
2807
2942
|
sendJson(res, { success: false, error: '缺少 aid' });
|
|
2808
2943
|
return;
|
|
2809
2944
|
}
|
|
2945
|
+
// 验证目标 Agent 是否存在
|
|
2946
|
+
try {
|
|
2947
|
+
const agentMdUrl = `https://${targetAid}/agent.md`;
|
|
2948
|
+
const checkRes = await fetch(agentMdUrl, { method: 'GET', signal: AbortSignal.timeout(5000) });
|
|
2949
|
+
if (!checkRes.ok) {
|
|
2950
|
+
sendJson(res, { success: false, error: '该 AGENT 不存在,添加失败' });
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
catch (_b) {
|
|
2955
|
+
sendJson(res, { success: false, error: '该 AGENT 不存在,添加失败' });
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2810
2958
|
// 自动上线
|
|
2811
2959
|
const instance = await ensureOnline(aid);
|
|
2812
2960
|
if (!instance.agentWS) {
|
|
@@ -3156,7 +3304,7 @@ async function handleRequest(req, res) {
|
|
|
3156
3304
|
await ensureGroupClient(instance);
|
|
3157
3305
|
// 只读本地缓存,不再每次请求都去服务端拉取
|
|
3158
3306
|
// 新消息通过 WebSocket 推送实时到达并由 SDK 自动存储
|
|
3159
|
-
const messages = instance.agentCP.getLocalGroupMessages(groupId);
|
|
3307
|
+
const messages = instance.agentCP.getLocalGroupMessages(groupId) || [];
|
|
3160
3308
|
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
3309
|
sendJson(res, { success: true, messages });
|
|
3162
3310
|
}
|
|
@@ -3208,7 +3356,7 @@ async function handleRequest(req, res) {
|
|
|
3208
3356
|
let groupName = groupId;
|
|
3209
3357
|
try {
|
|
3210
3358
|
const info = await instance.agentCP.groupOps.getGroupInfo(targetAid, groupId);
|
|
3211
|
-
groupName = info.name || groupId;
|
|
3359
|
+
groupName = (info && info.name) || groupId;
|
|
3212
3360
|
}
|
|
3213
3361
|
catch (_) { }
|
|
3214
3362
|
instance.agentCP.addGroupToStore(groupId, groupName);
|
|
@@ -3319,11 +3467,11 @@ async function handleRequest(req, res) {
|
|
|
3319
3467
|
const result = await ops.listMyGroups(target);
|
|
3320
3468
|
// 尝试获取每个群的详细信息(名称等)
|
|
3321
3469
|
const groups = [];
|
|
3322
|
-
for (const m of result.groups) {
|
|
3470
|
+
for (const m of (result.groups || [])) {
|
|
3323
3471
|
let name = m.group_id;
|
|
3324
3472
|
try {
|
|
3325
3473
|
const info = await ops.getGroupInfo(target, m.group_id);
|
|
3326
|
-
name = info.name || m.group_id;
|
|
3474
|
+
name = (info && info.name) || m.group_id;
|
|
3327
3475
|
}
|
|
3328
3476
|
catch (_) { }
|
|
3329
3477
|
groups.push(Object.assign(Object.assign({}, m), { name }));
|