aicq-chat-plugin 2.6.0 → 2.6.1

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/README.md CHANGED
@@ -5,12 +5,17 @@ AICQ 端到端加密聊天插件 — 适用于 OpenClaw 的完整聊天 UI。
5
5
  ## 一键安装
6
6
 
7
7
  ```bash
8
- # 使用 npx 直接运行(无需安装)
9
- npx aicq-chat-plugin
8
+ # 1. 卸载旧版
9
+ openclaw plugins uninstall aicq-chat
10
10
 
11
- # 或全局安装
12
- npm install -g aicq-chat-plugin
13
- aicq-plugin
11
+ # 2. 安装新版
12
+ openclaw plugins install npm:aicq-chat-plugin
13
+
14
+ # 3. 重启 gateway
15
+ openclaw gateway restart
16
+
17
+ # 4. 浏览器访问聊天界面
18
+ open http://localhost:6109
14
19
  ```
15
20
 
16
21
  ## 功能
@@ -46,7 +51,7 @@ aicq-plugin status
46
51
  | 变量 | 默认值 | 说明 |
47
52
  |------|--------|------|
48
53
  | `AICQ_PORT` | 6109 | 插件服务端口 |
49
- | `AICQ_SERVER_URL` | http://aicq.online:61018 | AICQ 服务器地址 |
54
+ | `AICQ_SERVER_URL` | https://aicq.online | AICQ 服务器地址 |
50
55
  | `AICQ_DATA_DIR` | ~/.aicq-plugin | 数据存储目录 |
51
56
 
52
57
  ## OpenClaw 集成
package/SKILL.md CHANGED
@@ -24,12 +24,17 @@ AICQ 是一个端到端加密聊天插件,适用于 OpenClaw 的完整聊天 U
24
24
  ## 一键启动
25
25
 
26
26
  ```bash
27
- # 使用 npx 直接运行(推荐,无需安装)
28
- npx aicq-chat-plugin
27
+ # 1. 卸载旧版
28
+ openclaw plugins uninstall aicq-chat
29
29
 
30
- # 或全局安装
31
- npm install -g aicq-chat-plugin
32
- aicq-plugin
30
+ # 2. 安装新版
31
+ openclaw plugins install npm:aicq-chat-plugin
32
+
33
+ # 3. 重启 gateway
34
+ openclaw gateway restart
35
+
36
+ # 4. 浏览器访问聊天界面
37
+ open http://localhost:6109
33
38
  ```
34
39
 
35
40
  ## OpenClaw 集成
@@ -53,7 +58,7 @@ aicq-plugin
53
58
  | 变量 | 默认值 | 说明 |
54
59
  |------|--------|------|
55
60
  | `AICQ_PORT` | 6109 | 插件服务端口 |
56
- | `AICQ_SERVER_URL` | http://aicq.online:61018 | AICQ 服务器地址 |
61
+ | `AICQ_SERVER_URL` | https://aicq.online | AICQ 服务器地址 |
57
62
  | `AICQ_DATA_DIR` | ~/.aicq-plugin | 数据存储目录 |
58
63
 
59
64
  ## Chat UI
package/cli.js CHANGED
@@ -3,8 +3,10 @@
3
3
  * AICQ Chat Plugin — CLI Entry Point
4
4
  *
5
5
  * Usage:
6
- * npx aicq-chat-plugin Start plugin + auto-install to OpenClaw
7
- * aicq-plugin Same as above
6
+ * openclaw plugins install npm:aicq-chat-plugin Install via openclaw CLI
7
+ * openclaw plugins uninstall aicq-chat Uninstall old version
8
+ * openclaw gateway restart Restart gateway
9
+ * aicq-plugin Start plugin server
8
10
  * aicq-plugin start Start the plugin server
9
11
  * aicq-plugin install Install plugin to OpenClaw only
10
12
  * aicq-plugin uninstall Remove plugin from OpenClaw
@@ -22,7 +24,7 @@ const command = args[0] || 'start';
22
24
 
23
25
  // Parse options
24
26
  let port = process.env.AICQ_PORT || '6109';
25
- let serverUrl = process.env.AICQ_SERVER_URL || 'http://aicq.online:61018';
27
+ let serverUrl = process.env.AICQ_SERVER_URL || 'https://aicq.online';
26
28
 
27
29
  for (let i = 0; i < args.length; i++) {
28
30
  if ((args[i] === '--port' || args[i] === '-p') && args[i + 1]) {
@@ -62,12 +64,17 @@ AICQ 是一个端到端加密聊天插件,适用于 OpenClaw 的完整聊天 U
62
64
  ## 一键启动
63
65
 
64
66
  \`\`\`bash
65
- # 使用 npx 直接运行(推荐,无需安装)
66
- npx aicq-chat-plugin
67
+ # 1. 卸载旧版
68
+ openclaw plugins uninstall aicq-chat
67
69
 
68
- # 或全局安装
69
- npm install -g aicq-chat-plugin
70
- aicq-plugin
70
+ # 2. 安装新版
71
+ openclaw plugins install npm:aicq-chat-plugin
72
+
73
+ # 3. 重启 gateway
74
+ openclaw gateway restart
75
+
76
+ # 4. 浏览器访问聊天界面
77
+ open http://localhost:6109
71
78
  \`\`\`
72
79
 
73
80
  ## OpenClaw 集成
@@ -91,7 +98,7 @@ aicq-plugin
91
98
  | 变量 | 默认值 | 说明 |
92
99
  |------|--------|------|
93
100
  | \`AICQ_PORT\` | 6109 | 插件服务端口 |
94
- | \`AICQ_SERVER_URL\` | http://aicq.online:61018 | AICQ 服务器地址 |
101
+ | \`AICQ_SERVER_URL\` | https://aicq.online | AICQ 服务器地址 |
95
102
  | \`AICQ_DATA_DIR\` | ~/.aicq-plugin | 数据存储目录 |
96
103
 
97
104
  ## Chat UI
@@ -389,8 +396,10 @@ if (command === '--help' || command === '-h') {
389
396
  AICQ Chat Plugin — End-to-End Encrypted Chat for OpenClaw
390
397
 
391
398
  Usage:
392
- npx aicq-chat-plugin Start plugin + auto-install to OpenClaw
393
- aicq-plugin [command] [options]
399
+ openclaw plugins install npm:aicq-chat-plugin Install plugin via openclaw CLI
400
+ openclaw plugins uninstall aicq-chat Uninstall old version
401
+ openclaw gateway restart Restart gateway after install
402
+ aicq-plugin [command] [options] Advanced usage
394
403
 
395
404
  Commands:
396
405
  start Install to OpenClaw (if needed) and start plugin server (default)
@@ -400,7 +409,7 @@ Commands:
400
409
 
401
410
  Options:
402
411
  --port, -p <port> Plugin server port (default: 6109)
403
- --server, -s <url> AICQ server URL (default: http://aicq.online:61018)
412
+ --server, -s <url> AICQ server URL (default: https://aicq.online)
404
413
  --help, -h Show this help message
405
414
 
406
415
  Environment Variables:
@@ -411,12 +420,13 @@ Environment Variables:
411
420
  OPENCLAW_WORKSPACE OpenClaw workspace directory (for skills/)
412
421
 
413
422
  Examples:
414
- npx aicq-chat-plugin # Install + start
415
- aicq-plugin # Start on default port
416
- aicq-plugin install # Install to OpenClaw only
417
- aicq-plugin uninstall # Remove from OpenClaw
418
- aicq-plugin --port 8080 # Start on port 8080
419
- aicq-plugin -s http://localhost # Connect to local server
423
+ openclaw plugins install npm:aicq-chat-plugin # Install via openclaw CLI
424
+ openclaw gateway restart # Restart gateway
425
+ aicq-plugin # Start on default port
426
+ aicq-plugin install # Install to OpenClaw only
427
+ aicq-plugin uninstall # Remove from OpenClaw
428
+ aicq-plugin --port 8080 # Start on port 8080
429
+ aicq-plugin -s http://localhost # Connect to local server
420
430
  `);
421
431
  process.exit(0);
422
432
  }
@@ -443,7 +453,7 @@ if (command === 'status') {
443
453
  });
444
454
  req.on('error', () => {
445
455
  console.log(`AICQ Plugin is not running on port ${port}.`);
446
- console.log(`Start it with: npx aicq-chat-plugin`);
456
+ console.log(`Start it with: openclaw plugins install npm:aicq-chat-plugin`);
447
457
  });
448
458
  req.setTimeout(3000, () => {
449
459
  req.destroy();
package/extension.js CHANGED
File without changes
package/index.js CHANGED
@@ -15,7 +15,7 @@ const PluginDatabase = require('./lib/database');
15
15
 
16
16
  // ─── Configuration ──────────────────────────────────────────────────
17
17
  const PORT = parseInt(process.env.AICQ_PORT || '6109', 10);
18
- const SERVER_URL = process.env.AICQ_SERVER_URL || 'http://aicq.online:61018';
18
+ const SERVER_URL = process.env.AICQ_SERVER_URL || 'https://aicq.online';
19
19
  const DATA_DIR = process.env.AICQ_DATA_DIR || path.join(os.homedir(), '.aicq-plugin');
20
20
  const UPLOADS_DIR = path.join(DATA_DIR, 'uploads');
21
21
 
@@ -364,8 +364,10 @@ fs.mkdirSync(UPLOADS_DIR, { recursive: true });
364
364
  if (!streamTarget) return res.status(400).json({ error: 'targetId or friend_id is required' });
365
365
  if (!data) return res.status(400).json({ error: 'data is required' });
366
366
  const type = chunk_type || chunkType || 'text';
367
- if (!['text', 'reasoning', 'tool_call', 'tool_result'].includes(type)) {
368
- return res.status(400).json({ error: `Invalid chunk_type: ${type}` });
367
+ // Allowed chunk types extended to include thinking and clear_text
368
+ const ALLOWED_CHUNK_TYPES = ['text', 'reasoning', 'thinking', 'clear_text', 'tool_call', 'tool_result'];
369
+ if (!ALLOWED_CHUNK_TYPES.includes(type)) {
370
+ return res.status(400).json({ error: `Invalid chunk_type: ${type}. Allowed: ${ALLOWED_CHUNK_TYPES.join(', ')}` });
369
371
  }
370
372
  const sent = serverClient.sendWS({
371
373
  type: 'stream_chunk',
@@ -477,7 +479,7 @@ fs.mkdirSync(UPLOADS_DIR, { recursive: true });
477
479
  // Avatar upload
478
480
  const avatarUpload = multer({
479
481
  storage: multer.memoryStorage(),
480
- limits: { fileSize: 2 * 1024 * 1024 }, // 2MB max
482
+ limits: { fileSize: 5 * 1024 * 1024 }, // 5MB max (client should resize before uploading)
481
483
  fileFilter: (req, file, cb) => {
482
484
  if (file.mimetype && file.mimetype.startsWith('image/')) {
483
485
  cb(null, true);
@@ -634,7 +636,9 @@ fs.mkdirSync(UPLOADS_DIR, { recursive: true });
634
636
  if (!kwargs.friend_id && !kwargs.targetId) return { error: 'friend_id or targetId is required' };
635
637
  if (!kwargs.data) return { error: 'data is required' };
636
638
  const chunkType = kwargs.chunk_type || kwargs.chunkType || 'text';
637
- if (!['text', 'reasoning', 'tool_call', 'tool_result'].includes(chunkType)) return { error: `Invalid chunk_type: ${chunkType}` };
639
+ // Allowed chunk types extended to include thinking and clear_text
640
+ const ALLOWED_CHUNK_TYPES = ['text', 'reasoning', 'thinking', 'clear_text', 'tool_call', 'tool_result'];
641
+ if (!ALLOWED_CHUNK_TYPES.includes(chunkType)) return { error: `Invalid chunk_type: ${chunkType}. Allowed: ${ALLOWED_CHUNK_TYPES.join(', ')}` };
638
642
  const streamTarget = kwargs.friend_id || kwargs.targetId;
639
643
  const sent = serverClient.sendWS({
640
644
  type: 'stream_chunk',
package/lib/chat.js CHANGED
File without changes
package/lib/crypto.js CHANGED
File without changes
package/lib/database.js CHANGED
File without changes
File without changes
package/lib/handshake.js CHANGED
File without changes
package/lib/identity.js CHANGED
File without changes
@@ -6,7 +6,7 @@ const fetch = require('node-fetch');
6
6
  const { signMessage, computeFingerprint } = require('./crypto');
7
7
 
8
8
  class ServerClient {
9
- constructor(identityManager, db, serverUrl = 'http://aicq.online:61018') {
9
+ constructor(identityManager, db, serverUrl = 'https://aicq.online') {
10
10
  this.identity = identityManager;
11
11
  this.db = db;
12
12
  this.serverUrl = serverUrl;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "aicq-chat",
3
3
  "name": "AICQ Encrypted Chat",
4
- "version": "2.6.0",
4
+ "version": "2.6.1",
5
5
  "description": "End-to-end encrypted chat plugin for OpenClaw agents — Node.js implementation with full UI",
6
6
  "entry": "index.js",
7
7
  "activation": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicq-chat-plugin",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "AICQ End-to-end Encrypted Chat Plugin for OpenClaw — Full UI with friend management, group chat, file transfer, and AI agent communication",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -37,11 +37,11 @@
37
37
  "license": "MIT",
38
38
  "repository": {
39
39
  "type": "git",
40
- "url": "git+https://github.com/ctz168/aicq.git",
41
- "directory": "plugin-js"
40
+ "url": "git+https://github.com/ctz168/pluginAICQ.git",
41
+ "directory": "openclaw-plugin"
42
42
  },
43
43
  "bugs": {
44
- "url": "https://github.com/ctz168/aicq/issues"
44
+ "url": "https://github.com/ctz168/pluginAICQ/issues"
45
45
  },
46
46
  "homepage": "https://aicq.online",
47
47
  "dependencies": {
package/postinstall.js CHANGED
@@ -42,7 +42,17 @@ AICQ 是一个端到端加密聊天插件,适用于 OpenClaw 的完整聊天 U
42
42
  ## 一键启动
43
43
 
44
44
  \`\`\`bash
45
- npx aicq-chat-plugin
45
+ # 1. 卸载旧版
46
+ openclaw plugins uninstall aicq-chat
47
+
48
+ # 2. 安装新版
49
+ openclaw plugins install npm:aicq-chat-plugin
50
+
51
+ # 3. 重启 gateway
52
+ openclaw gateway restart
53
+
54
+ # 4. 浏览器访问聊天界面
55
+ open http://localhost:6109
46
56
  \`\`\`
47
57
 
48
58
  ## OpenClaw 集成
@@ -66,7 +76,7 @@ npx aicq-chat-plugin
66
76
  | 变量 | 默认值 | 说明 |
67
77
  |------|--------|------|
68
78
  | \`AICQ_PORT\` | 6109 | 插件服务端口 |
69
- | \`AICQ_SERVER_URL\` | http://aicq.online:61018 | AICQ 服务器地址 |
79
+ | \`AICQ_SERVER_URL\` | https://aicq.online | AICQ 服务器地址 |
70
80
  | \`AICQ_DATA_DIR\` | ~/.aicq-plugin | 数据存储目录 |
71
81
 
72
82
  ## Chat UI
@@ -335,8 +345,8 @@ if (skillInstalled || pluginInstalled) {
335
345
  console.log(' ║ OPENCLAW_HOME=<openclaw-root> ║');
336
346
  console.log(' ║ OPENCLAW_WORKSPACE=<workspace-dir> ║');
337
347
  console.log(' ║ ║');
338
- console.log(' ║ Or start standalone: ║');
339
- console.log(' ║ npx aicq-chat-plugin ║');
348
+ console.log(' ║ Or install via openclaw CLI: ║');
349
+ console.log(' ║ openclaw plugins install npm:aicq-chat-plugin ║');
340
350
  console.log(' ║ ║');
341
351
  console.log(' ║ Chat UI: http://localhost:6109 ║');
342
352
  console.log(' ║ Docs: https://aicq.online ║');
Binary file
Binary file
Binary file
package/public/index.html CHANGED
@@ -3,131 +3,142 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="theme-color" content="#D97757">
6
7
  <title>AICQ 加密聊天</title>
8
+ <link rel="icon" type="image/png" sizes="32x32" href="/icon-32.png">
9
+ <link rel="icon" type="image/png" sizes="16x16" href="/icon-16.png">
10
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
7
11
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
8
12
  <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
9
13
  <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
10
14
  <script src="https://cdn.jsdelivr.net/npm/marked@12.0.0/marked.min.js"></script>
11
15
  <style>
12
16
  *{margin:0;padding:0;box-sizing:border-box}
13
- :root{--primary:#4f46e5;--primary-light:#6366f1;--bg:#0f172a;--bg2:#1e293b;--bg3:#334155;--text:#f1f5f9;--text2:#94a3b8;--border:#475569;--success:#22c55e;--danger:#ef4444;--warning:#f59e0b;--info:#3b82f6;--bubble-me:#4f46e5;--bubble-them:#334155}
14
- body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans SC",sans-serif;background:var(--bg);color:var(--text);display:flex;height:100vh;overflow:hidden}
17
+ :root{--primary:#D97757;--primary-light:#C4613F;--primary-lighter:rgba(217,119,87,0.1);--accent:#D97757;--accent-hover:#C4613F;--accent-light:rgba(217,119,87,0.1);--bg:#FAF9F6;--bg2:#F5F1EB;--bg-warm:#F5F1EB;--bg3:#FFFFFF;--bg-card:#FFFFFF;--text:#2D2A26;--text2:#6B6560;--text-sec:#6B6560;--text-muted:#9B958E;--border:#E8DFD3;--beige:#E8DFD3;--success:#4CAF7D;--green:#4CAF7D;--danger:#E05555;--red:#E05555;--warning:#E5A54B;--amber:#E5A54B;--brown:#8B6F4E;--brown-light:rgba(139,111,78,0.1);--cream:#F0EAE0;--info:#5B8DEF;--blue:#5B8DEF;--purple:#7B6CB0;--bubble-me:#D97757;--bubble-them:#FFFFFF;--shadow-sm:0 1px 3px rgba(45,42,38,0.06);--shadow-md:0 4px 16px rgba(45,42,38,0.08);--shadow-lg:0 8px 32px rgba(45,42,38,0.1);--radius:12px;--radius-sm:8px;--radius-lg:20px;--transition:all 0.25s cubic-bezier(0.4,0,0.2,1)}
18
+ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans SC",sans-serif;background:var(--bg);color:var(--text);display:flex;height:100vh;overflow:hidden;-webkit-font-smoothing:antialiased}
15
19
  /* Layout */
16
20
  .app{display:flex;width:100%;height:100%}
17
- .right-panel{width:280px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
21
+ .right-panel{width:280px;background:var(--bg3);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
18
22
  .main-panel{flex:1;display:flex;flex-direction:column;min-width:0}
19
23
  .chat-area{flex:1;overflow:hidden;display:flex;flex-direction:column}
20
24
  /* Right Panel */
21
25
  .agent-select{padding:12px;border-bottom:1px solid var(--border)}
22
- .agent-select select{width:100%;padding:8px 12px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:6px;font-size:14px;cursor:pointer}
26
+ .agent-select select{width:100%;padding:8px 12px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:10px;font-size:14px;cursor:pointer}
23
27
  .action-buttons{display:flex;gap:6px;padding:10px 12px;border-bottom:1px solid var(--border)}
24
- .action-btn{flex:1;padding:7px 4px;background:var(--bg3);color:var(--text2);border:none;border-radius:6px;cursor:pointer;font-size:12px;display:flex;flex-direction:column;align-items:center;gap:3px;transition:all .2s}
25
- .action-btn:hover{background:var(--primary);color:#fff}
28
+ .action-btn{flex:1;padding:7px 4px;background:var(--cream);color:var(--text2);border:1px solid var(--border);border-radius:10px;cursor:pointer;font-size:12px;display:flex;flex-direction:column;align-items:center;gap:3px;transition:var(--transition)}
29
+ .action-btn:hover{background:var(--primary);color:#fff;border-color:var(--primary)}
26
30
  .action-btn .icon{font-size:18px}
27
31
  .list-section{flex:1;overflow-y:auto}
28
- .list-section h4{padding:10px 12px 6px;color:var(--text2);font-size:12px;text-transform:uppercase;letter-spacing:1px}
29
- .friend-item,.group-item{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;transition:background .15s;border-left:3px solid transparent}
30
- .friend-item:hover,.group-item:hover{background:var(--bg3)}
31
- .friend-item.active,.group-item.active{background:rgba(79,70,229,.2);border-left-color:var(--primary)}
32
- .avatar{width:36px;height:36px;border-radius:50%;background:var(--primary);display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:600;flex-shrink:0;color:#fff;overflow:hidden}
33
- .avatar img{width:100%;height:100%;object-fit:cover;border-radius:50%}
32
+ .list-section h4{padding:10px 12px 6px;color:var(--text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px}
33
+ .friend-item,.group-item{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;transition:var(--transition);border-left:3px solid transparent}
34
+ .friend-item:hover,.group-item:hover{background:var(--cream)}
35
+ .friend-item.active,.group-item.active{background:var(--primary-lighter);border-left-color:var(--primary)}
36
+ .avatar{width:36px;height:36px;border-radius:12px;background:linear-gradient(135deg,var(--primary),var(--brown));display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:600;flex-shrink:0;color:#fff;overflow:hidden}
37
+ .avatar img{width:100%;height:100%;object-fit:cover;border-radius:12px}
34
38
  .avatar.online{box-shadow:0 0 0 2px var(--success)}
35
39
  .info{flex:1;min-width:0}
36
40
  .info .name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
37
41
  .info .status{font-size:11px;color:var(--text2)}
38
42
  .badge-online{color:var(--success);font-size:10px}
39
43
  .badge-offline{color:var(--text2);font-size:10px}
40
- .silent-badge{font-size:9px;background:var(--warning);color:#000;padding:1px 4px;border-radius:3px;margin-left:4px}
44
+ .silent-badge{font-size:9px;background:var(--amber);color:#fff;padding:1px 4px;border-radius:4px;margin-left:4px}
41
45
  /* Chat Header */
42
- .chat-header{padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;background:var(--bg2)}
46
+ .chat-header{padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;background:var(--bg3)}
43
47
  .chat-header .avatar{width:40px;height:40px;font-size:16px}
44
48
  .chat-header .info .name{font-size:16px}
45
49
  .chat-header .actions{margin-left:auto;display:flex;gap:8px}
46
- .chat-header .actions button{background:none;border:none;color:var(--text2);cursor:pointer;font-size:18px;padding:4px 8px;border-radius:4px}
47
- .chat-header .actions button:hover{background:var(--bg3);color:var(--text)}
50
+ .chat-header .actions button{background:none;border:none;color:var(--text2);cursor:pointer;font-size:18px;padding:4px 8px;border-radius:var(--radius-sm);transition:var(--transition)}
51
+ .chat-header .actions button:hover{background:var(--cream);color:var(--primary)}
48
52
  /* Messages */
49
- .messages-container{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:4px}
53
+ .messages-container{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:4px;background:var(--bg2)}
50
54
  .load-more{text-align:center;padding:8px;color:var(--text2);font-size:12px;cursor:pointer}
51
55
  .load-more:hover{color:var(--primary)}
52
56
  .msg-row{display:flex;max-width:75%;gap:8px}
53
57
  .msg-row.me{margin-left:auto;flex-direction:row-reverse}
54
58
  .msg-row.them{margin-right:auto}
55
- .msg-bubble{padding:10px 14px;border-radius:12px;font-size:14px;line-height:1.6;word-break:break-word;position:relative;max-width:100%}
56
- .msg-row.me .msg-bubble{background:var(--bubble-me);color:#fff;border-bottom-right-radius:4px}
57
- .msg-row.them .msg-bubble{background:var(--bubble-them);color:var(--text);border-bottom-left-radius:4px}
59
+ .msg-bubble{padding:10px 14px;border-radius:14px;font-size:14px;line-height:1.6;word-break:break-word;position:relative;max-width:100%}
60
+ .msg-row.me .msg-bubble{background:var(--bubble-me);color:#fff;border-top-right-radius:4px}
61
+ .msg-row.them .msg-bubble{background:var(--bubble-them);border:1px solid var(--border);color:var(--text);border-top-left-radius:4px}
58
62
  .msg-bubble img{max-width:300px;max-height:300px;border-radius:8px;margin-top:6px;cursor:pointer}
59
63
  .msg-bubble .file-link{display:flex;align-items:center;gap:6px;padding:6px 10px;background:rgba(255,255,255,.1);border-radius:6px;margin-top:6px;cursor:pointer;color:inherit;text-decoration:none}
60
64
  .msg-bubble .file-link:hover{background:rgba(255,255,255,.2)}
61
65
  .msg-time{font-size:10px;color:var(--text2);margin-top:4px;text-align:right}
66
+ .msg-row.me .msg-time{color:rgba(255,255,255,0.6)}
62
67
  .msg-row.them .msg-time{text-align:left}
63
68
  .msg-actions{position:absolute;top:-8px;right:-4px;display:none;gap:2px}
64
69
  .msg-row.me .msg-actions{right:auto;left:-4px}
65
70
  .msg-row:hover .msg-actions{display:flex}
66
- .msg-action-btn{width:24px;height:24px;border-radius:50%;background:var(--bg3);border:none;color:var(--text2);cursor:pointer;font-size:11px;display:flex;align-items:center;justify-content:center}
67
- .msg-action-btn:hover{background:var(--primary);color:#fff}
68
- .msg-action-btn.danger:hover{background:var(--danger)}
71
+ .msg-action-btn{width:24px;height:24px;border-radius:var(--radius-sm);background:var(--bg3);border:1px solid var(--border);color:var(--text2);cursor:pointer;font-size:11px;display:flex;align-items:center;justify-content:center;transition:var(--transition)}
72
+ .msg-action-btn:hover{background:var(--primary);color:#fff;border-color:var(--primary)}
73
+ .msg-action-btn.danger:hover{background:var(--danger);border-color:var(--danger)}
69
74
  /* Mention */
70
75
  .mention{color:var(--info);font-weight:600;cursor:pointer}
71
76
  .mention:hover{text-decoration:underline}
72
77
  /* Input Area */
73
- .input-area{padding:12px 16px;border-top:1px solid var(--border);background:var(--bg2);display:flex;flex-direction:column;gap:8px}
78
+ .input-area{padding:12px 16px;border-top:1px solid var(--border);background:var(--bg3);display:flex;flex-direction:column;gap:8px}
74
79
  .input-top{display:flex;gap:8px;align-items:center}
75
- .input-top input{flex:1;padding:10px 14px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px;outline:none}
76
- .input-top input:focus{border-color:var(--primary)}
77
- .send-btn{padding:10px 20px;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:14px;font-weight:500;transition:background .2s}
78
- .send-btn:hover{background:var(--primary-light)}
79
- .send-btn:disabled{opacity:.5;cursor:not-allowed}
80
+ .input-top input{flex:1;padding:10px 14px;background:var(--bg);border:1px solid var(--border);border-radius:12px;color:var(--text);font-size:14px;outline:none;transition:var(--transition)}
81
+ .input-top input:focus{border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-lighter)}
82
+ .send-btn{padding:10px 20px;background:var(--primary);color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:14px;font-weight:500;box-shadow:0 4px 16px rgba(217,119,87,0.3);transition:var(--transition)}
83
+ .send-btn:hover{background:var(--primary-light);transform:scale(1.05)}
84
+ .send-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}
80
85
  .input-toolbar{display:flex;gap:4px}
81
- .tool-btn{background:none;border:none;color:var(--text2);cursor:pointer;font-size:16px;padding:4px 8px;border-radius:4px}
82
- .tool-btn:hover{background:var(--bg3);color:var(--text)}
86
+ .tool-btn{background:none;border:none;color:var(--text2);cursor:pointer;font-size:16px;padding:4px 8px;border-radius:var(--radius-sm);transition:var(--transition)}
87
+ .tool-btn:hover{background:var(--cream);color:var(--primary)}
83
88
  /* Modals */
84
- .modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);display:none;align-items:center;justify-content:center;z-index:1000}
89
+ .modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(45,42,38,0.4);display:none;align-items:center;justify-content:center;z-index:1000}
85
90
  .modal-overlay.show{display:flex}
86
- .modal{background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:24px;max-width:480px;width:90%;max-height:80vh;overflow-y:auto}
91
+ .modal{background:var(--bg3);border:1px solid var(--border);border-radius:20px;padding:24px;max-width:480px;width:90%;max-height:80vh;overflow-y:auto;box-shadow:var(--shadow-lg)}
87
92
  .modal h3{margin-bottom:16px;font-size:18px}
88
93
  .modal .form-group{margin-bottom:14px}
89
94
  .modal label{display:block;margin-bottom:6px;font-size:13px;color:var(--text2)}
90
- .modal input,.modal select,.modal textarea{width:100%;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:14px}
95
+ .modal input,.modal select,.modal textarea{width:100%;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:14px;transition:var(--transition)}
96
+ .modal input:focus,.modal select:focus,.modal textarea:focus{border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-lighter);outline:none}
91
97
  .modal textarea{min-height:80px;resize:vertical}
92
98
  .modal .btn-row{display:flex;gap:8px;justify-content:flex-end;margin-top:16px}
93
- .modal .btn{padding:8px 16px;border:none;border-radius:6px;cursor:pointer;font-size:14px;transition:all .2s}
99
+ .modal .btn{padding:8px 16px;border:none;border-radius:10px;cursor:pointer;font-size:14px;transition:var(--transition)}
94
100
  .modal .btn-primary{background:var(--primary);color:#fff}
95
101
  .modal .btn-primary:hover{background:var(--primary-light)}
96
- .modal .btn-secondary{background:var(--bg3);color:var(--text)}
102
+ .modal .btn-secondary{background:var(--cream);color:var(--text);border:1px solid var(--border)}
103
+ .modal .btn-secondary:hover{background:var(--primary-lighter);border-color:var(--primary);color:var(--primary)}
97
104
  .modal .btn-danger{background:var(--danger);color:#fff}
98
105
  .qr-container{text-align:center;margin:16px 0}
99
106
  .qr-container img{max-width:256px;border-radius:8px}
100
- .key-display{background:var(--bg);padding:10px;border-radius:6px;font-family:monospace;font-size:12px;word-break:break-all;margin:8px 0;color:var(--text2)}
101
- .warning-box{background:rgba(239,68,68,.15);border:1px solid var(--danger);border-radius:6px;padding:12px;margin:12px 0;color:#fca5a5;font-size:13px}
107
+ .key-display{background:var(--bg);padding:10px;border-radius:var(--radius-sm);font-family:monospace;font-size:12px;word-break:break-all;margin:8px 0;color:var(--text-muted)}
108
+ .warning-box{background:rgba(224,85,85,0.08);border:1px solid var(--danger);border-radius:var(--radius-sm);padding:12px;margin:12px 0;color:var(--danger);font-size:13px}
102
109
  /* Empty State */
103
- .empty-state{flex:1;display:flex;align-items:center;justify-content:center;color:var(--text2);font-size:16px;flex-direction:column;gap:12px}
110
+ .empty-state{flex:1;display:flex;align-items:center;justify-content:center;color:var(--text-muted);font-size:16px;flex-direction:column;gap:12px}
104
111
  .empty-state .icon{font-size:48px;opacity:.3}
105
112
  /* Mention dropdown */
106
- .mention-dropdown{position:absolute;bottom:100%;left:0;background:var(--bg2);border:1px solid var(--border);border-radius:6px;max-height:200px;overflow-y:auto;z-index:100;display:none;min-width:200px}
113
+ .mention-dropdown{position:absolute;bottom:100%;left:0;background:var(--bg3);border:1px solid var(--border);border-radius:10px;max-height:200px;overflow-y:auto;z-index:100;display:none;min-width:200px;box-shadow:var(--shadow-md)}
107
114
  .mention-dropdown.show{display:block}
108
115
  .mention-option{padding:8px 12px;cursor:pointer;font-size:13px;display:flex;align-items:center;gap:8px}
109
- .mention-option:hover{background:var(--bg3)}
116
+ .mention-option:hover{background:var(--cream)}
110
117
  /* Scrollbar */
111
118
  ::-webkit-scrollbar{width:6px}
112
- ::-webkit-scrollbar-track{background:var(--bg)}
119
+ ::-webkit-scrollbar-track{background:transparent}
113
120
  ::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
114
- ::-webkit-scrollbar-thumb:hover{background:var(--text2)}
121
+ ::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}
115
122
  /* Markdown in bubbles */
116
123
  .msg-bubble p{margin:4px 0}
117
- .msg-bubble code{background:rgba(255,255,255,.1);padding:1px 4px;border-radius:3px;font-size:13px}
118
- .msg-bubble pre{background:rgba(0,0,0,.2);padding:8px;border-radius:6px;overflow-x:auto;margin:6px 0}
124
+ .msg-row.them .msg-bubble code{background:rgba(0,0,0,0.05);padding:1px 4px;border-radius:3px;font-size:13px}
125
+ .msg-row.me .msg-bubble code{background:rgba(255,255,255,0.2);padding:1px 4px;border-radius:3px;font-size:13px}
126
+ .msg-row.them .msg-bubble pre{background:rgba(0,0,0,0.04);padding:8px;border-radius:6px;overflow-x:auto;margin:6px 0}
127
+ .msg-row.me .msg-bubble pre{background:rgba(255,255,255,0.15);padding:8px;border-radius:6px;overflow-x:auto;margin:6px 0}
119
128
  .msg-bubble pre code{background:none;padding:0}
120
129
  .msg-bubble blockquote{border-left:3px solid var(--primary);padding-left:10px;margin:6px 0;color:var(--text2)}
121
130
  .msg-bubble ul,.msg-bubble ol{padding-left:20px;margin:4px 0}
122
- .msg-bubble a{color:var(--info)}
131
+ .msg-row.them .msg-bubble a{color:var(--primary)}
132
+ .msg-row.me .msg-bubble a{color:#fff;text-decoration:underline}
123
133
  .msg-bubble table{border-collapse:collapse;margin:6px 0}
124
- .msg-bubble th,.msg-bubble td{border:1px solid var(--border);padding:4px 8px;font-size:13px}
134
+ .msg-bubble th{background:var(--cream);border:1px solid var(--border);padding:4px 8px;font-size:13px}
135
+ .msg-bubble td{border:1px solid var(--border);padding:4px 8px;font-size:13px}
125
136
  /* Toast */
126
- .toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);background:var(--bg2);border:1px solid var(--border);color:var(--text);padding:12px 24px;border-radius:8px;font-size:14px;z-index:9999;box-shadow:0 4px 20px rgba(0,0,0,.5);max-width:90%;text-align:center;opacity:0;transition:opacity .3s;pointer-events:none}
137
+ .toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:12px 24px;border-radius:var(--radius);font-size:14px;z-index:9999;box-shadow:var(--shadow-lg);max-width:90%;text-align:center;opacity:0;transition:opacity .3s;pointer-events:none}
127
138
  .toast.show{opacity:1}
128
- .toast.warning{border-color:var(--warning);background:rgba(245,158,11,.15);color:#fcd34d}
139
+ .toast.warning{border-color:var(--warning);background:rgba(229,165,75,0.1);color:var(--warning)}
129
140
  /* Backup section */
130
- .backup-section{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;margin-top:8px}
141
+ .backup-section{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:12px;margin-top:8px}
131
142
  .backup-section h4{font-size:14px;margin-bottom:8px;color:var(--text)}
132
143
  .backup-section p{font-size:12px;color:var(--text2);line-height:1.6;margin:4px 0}
133
144
  .backup-section .warning-box{font-size:12px;padding:8px;margin:8px 0}
@@ -137,7 +148,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans S
137
148
  .key-match-row{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--border)}
138
149
  .key-match-row:last-child{border-bottom:none}
139
150
  .key-match-row label{font-size:13px;color:var(--text2);min-width:120px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
140
- .key-match-row select{flex:1;padding:6px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px}
151
+ .key-match-row select{flex:1;padding:6px 10px;background:var(--bg);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:13px}
141
152
  </style>
142
153
  </head>
143
154
  <body>
@@ -993,20 +1004,37 @@ function showChatInfo() {
993
1004
  : `好友: ${currentTarget.name}\nID: ${currentTarget.id}\n状态: ${currentTarget.isOnline ? '在线' : '离线'}`);
994
1005
  }
995
1006
 
1007
+ // ─── Image Resize Utility ───────────────────────────────────────────
1008
+ function resizePluginImage(file,maxW,maxH,quality=0.8){
1009
+ return new Promise((resolve,reject)=>{
1010
+ if(!file.type.startsWith('image/')){reject(new Error('Not an image'));return;}
1011
+ const img=new Image();const url=URL.createObjectURL(file);
1012
+ img.onload=()=>{
1013
+ let w=img.width,h=img.height;
1014
+ if(w<=maxW&&h<=maxH&&file.size<=512*1024){URL.revokeObjectURL(url);resolve(file);return;}
1015
+ const ratio=Math.min(maxW/w,maxH/h,1);w=Math.round(w*ratio);h=Math.round(h*ratio);
1016
+ const canvas=document.createElement('canvas');canvas.width=w;canvas.height=h;
1017
+ const ctx=canvas.getContext('2d');ctx.drawImage(img,0,0,w,h);
1018
+ canvas.toBlob(blob=>{URL.revokeObjectURL(url);if(!blob){reject(new Error('Resize failed'));return;}resolve(new File([blob],file.name.replace(/\.\w+$/,'.jpg'),{type:'image/jpeg',lastModified:Date.now()}));},'image/jpeg',quality);
1019
+ };
1020
+ img.onerror=()=>{URL.revokeObjectURL(url);reject(new Error('Image load failed'));};
1021
+ img.src=url;
1022
+ });
1023
+ }
1024
+
996
1025
  // ─── Avatar Upload ──────────────────────────────────────────────────
997
1026
  async function handlePluginAvatarUpload(input) {
998
- const file = input.files && input.files[0];
999
- if (!file || !currentAgentId) return;
1000
- if (!file.type.startsWith('image/')) { alert('请选择图片文件'); return; }
1001
- if (file.size > 2 * 1024 * 1024) { alert('图片不能超过2MB'); return; }
1027
+ const rawFile = input.files && input.files[0];
1028
+ if (!rawFile || !currentAgentId) return;
1029
+ if (!rawFile.type.startsWith('image/')) { alert('请选择图片文件'); return; }
1002
1030
  try {
1031
+ const file = await resizePluginImage(rawFile, 256, 256, 0.85); // resize for avatar
1003
1032
  const formData = new FormData();
1004
1033
  formData.append('avatar', file);
1005
1034
  formData.append('agent_id', currentAgentId);
1006
1035
  const resp = await fetch(API + '/api/identity/avatar', { method: 'POST', body: formData });
1007
1036
  const data = await resp.json();
1008
1037
  if (data.success || data.avatar) {
1009
- // Update the settings avatar display
1010
1038
  const avatarUrl = data.avatar || (data.account && data.account.avatar);
1011
1039
  if (avatarUrl) {
1012
1040
  document.getElementById('settingsAvatar').innerHTML = `<img src="${avatarUrl}" alt="头像">`;
Binary file