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 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 || process.cwd();
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 sorted = [...batch.messages].sort((a, b) => a.msg_id - b.msg_id);
464
- utils_1.logger.log(`[AgentCP] processAndAckBatch: group=${groupId} batchCount=${batch.messages.length} sortedCount=${sorted.length} msgIds=[${sorted.map(m => m.msg_id).join(',')}]`);
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
- const lastMsgId = msgs[msgs.length - 1].msg_id;
685
- await this.groupOps.ackMessages(this._groupTargetAid, groupId, lastMsgId);
686
- // 更新 after 用于下一轮拉取
687
- after = lastMsgId;
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
  }
@@ -1,3 +1,5 @@
1
+ declare const DEFAULT_ACP_DIR: string;
2
+ export { DEFAULT_ACP_DIR };
1
3
  export declare class CertAndKeyStore {
2
4
  static aidKey: string;
3
5
  private static basePath;
@@ -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(process.cwd(), '.acp-data');
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 = process.cwd();
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) {
@@ -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
- if (result.messages.length > 0) {
211
- handler.onMessages(groupId, result.messages);
212
- const lastId = (_a = result.messages[result.messages.length - 1].msg_id) !== null && _a !== void 0 ? _a : after;
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
- if (result.events.length > 0) {
227
- handler.onEvents(groupId, result.events);
228
- const lastId = (_a = result.events[result.events.length - 1].event_id) !== null && _a !== void 0 ? _a : after;
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
- utils_1.logger.log(`[Group] onGroupMessageBatch: group=${groupId} count=${batch.count} range=[${batch.start_msg_id}, ${batch.latest_msg_id}] messages=${JSON.stringify((batch.messages || []).map(m => m.msg_id))}`);
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 || process.cwd();
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 || process.cwd();
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 || process.cwd(),
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 2px 12px rgba(37,99,235,0.06); }
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: translateX(-50%); padding: 12px 24px; border-radius: 10px; font-size: 14px; display: none; z-index: 1000; box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
707
- .status.success { display: block; background: #d1fae5; color: #065f46; }
708
- .status.error { display: block; background: #fee2e2; color: #991b1b; }
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
- setTimeout(function() { el.className = 'status'; }, 3000);
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:12px; font-size:14px; line-height:1.5; word-wrap:break-word; box-shadow:0 1px 2px rgba(0,0,0,0.05); }
1008
- .message.sent .bubble { background:var(--sent); color:#fff; border-bottom-right-radius:2px; }
1009
- .message.received .bubble { background:var(--recv-bg); color:var(--t1); border-bottom-left-radius:2px; border:1px solid var(--border); }
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; align-items:center; gap:10px; flex-shrink:0; }
1013
- .input-area input { flex:1; padding:10px 14px; border-radius:20px; border:1px solid var(--border); font-size:14px; background:#f9fafb; }
1014
- .input-area input:focus { outline:none; border-color:var(--primary); background:#fff; }
1015
- .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; }
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:disabled { background:#ccc; cursor:not-allowed; }
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:none; align-items:center; justify-content:center; }
1020
- .modal-overlay.show { display:flex; }
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:1;display:none;flex-direction:column;overflow:hidden;">
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
- <input type="text" id="messageInput" placeholder="输入消息..." onkeypress="if(event.key==='Enter')sendMessage()">
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(0,0,0,0.45);z-index:999;display:none;align-items:center;justify-content:center;flex-direction:column;gap:16px;">
1330
- <div style="width:36px;height:36px;border:3px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:spin 0.8s linear infinite;"></div>
1331
- <div id="switchAidMsg" style="color:#fff;font-size:15px;font-weight:500;">切换身份中...</div>
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(0,0,0,0.75);z-index:999;display:none;align-items:center;justify-content:center;flex-direction:column;gap:12px;padding:28px 40px;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,0.3);">
1334
- <div style="width:32px;height:32px;border:3px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:spin 0.8s linear infinite;"></div>
1335
- <div id="switchGroupMsg" style="color:#fff;font-size:14px;font-weight:500;">加载群组中...</div>
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
- if(!txt){ return; }
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'?'block':'none';
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 }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acp-ts",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "基于 ACP智能体通信协议 的智能体通信库,提供智能体身份管理和实时通信功能",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",