aicq-chat-plugin 2.6.0 → 2.6.2
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 +11 -6
- package/SKILL.md +11 -6
- package/cli.js +29 -19
- package/extension.js +0 -0
- package/index.js +9 -5
- package/lib/chat.js +0 -0
- package/lib/crypto.js +0 -0
- package/lib/database.js +0 -0
- package/lib/file-transfer.js +0 -0
- package/lib/handshake.js +0 -0
- package/lib/identity.js +0 -0
- package/lib/server-client.js +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -4
- package/postinstall.js +14 -4
- package/public/favicon.ico +0 -0
- package/public/icon-16.png +0 -0
- package/public/icon-32.png +0 -0
- package/public/index.html +84 -56
- package/public/logo-512.png +0 -0
package/README.md
CHANGED
|
@@ -5,12 +5,17 @@ AICQ 端到端加密聊天插件 — 适用于 OpenClaw 的完整聊天 UI。
|
|
|
5
5
|
## 一键安装
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
#
|
|
9
|
-
|
|
8
|
+
# 1. 卸载旧版
|
|
9
|
+
openclaw plugins uninstall aicq-chat
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
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` |
|
|
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
|
-
#
|
|
28
|
-
|
|
27
|
+
# 1. 卸载旧版
|
|
28
|
+
openclaw plugins uninstall aicq-chat
|
|
29
29
|
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
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` |
|
|
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
|
-
*
|
|
7
|
-
* aicq-
|
|
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 || '
|
|
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
|
-
#
|
|
66
|
-
|
|
67
|
+
# 1. 卸载旧版
|
|
68
|
+
openclaw plugins uninstall aicq-chat
|
|
67
69
|
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
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\` |
|
|
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
|
-
|
|
393
|
-
aicq-
|
|
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:
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
aicq-plugin
|
|
417
|
-
aicq-plugin
|
|
418
|
-
aicq-plugin
|
|
419
|
-
aicq-plugin
|
|
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:
|
|
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 || '
|
|
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
|
-
|
|
368
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
package/lib/file-transfer.js
CHANGED
|
File without changes
|
package/lib/handshake.js
CHANGED
|
File without changes
|
package/lib/identity.js
CHANGED
|
File without changes
|
package/lib/server-client.js
CHANGED
|
@@ -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 = '
|
|
9
|
+
constructor(identityManager, db, serverUrl = 'https://aicq.online') {
|
|
10
10
|
this.identity = identityManager;
|
|
11
11
|
this.db = db;
|
|
12
12
|
this.serverUrl = serverUrl;
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicq-chat-plugin",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.2",
|
|
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/
|
|
41
|
-
"directory": "plugin
|
|
40
|
+
"url": "git+https://github.com/ctz168/pluginAICQ.git",
|
|
41
|
+
"directory": "openclaw-plugin"
|
|
42
42
|
},
|
|
43
43
|
"bugs": {
|
|
44
|
-
"url": "https://github.com/ctz168/
|
|
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
|
-
|
|
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\` |
|
|
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
|
|
339
|
-
console.log(' ║
|
|
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:#
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
29
|
-
.friend-item,.group-item{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;transition:
|
|
30
|
-
.friend-item:hover,.group-item:hover{background:var(--
|
|
31
|
-
.friend-item.active,.group-item.active{background:
|
|
32
|
-
.avatar{width:36px;height:36px;border-radius:
|
|
33
|
-
.avatar img{width:100%;height:100%;object-fit:cover;border-radius:
|
|
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(--
|
|
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(--
|
|
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:
|
|
47
|
-
.chat-header .actions button:hover{background:var(--
|
|
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:
|
|
56
|
-
.msg-row.me .msg-bubble{background:var(--bubble-me);color:#fff;border-
|
|
57
|
-
.msg-row.them .msg-bubble{background:var(--bubble-them);color:var(--text);border-
|
|
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:
|
|
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(--
|
|
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:
|
|
76
|
-
.input-top input:focus{border-color:var(--primary)}
|
|
77
|
-
.send-btn{padding:10px 20px;background:var(--primary);color:#fff;border:none;border-radius:
|
|
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:
|
|
82
|
-
.tool-btn:hover{background:var(--
|
|
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(
|
|
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(--
|
|
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:
|
|
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:
|
|
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(--
|
|
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:
|
|
101
|
-
.warning-box{background:rgba(
|
|
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(--
|
|
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(--
|
|
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(--
|
|
116
|
+
.mention-option:hover{background:var(--cream)}
|
|
110
117
|
/* Scrollbar */
|
|
111
118
|
::-webkit-scrollbar{width:6px}
|
|
112
|
-
::-webkit-scrollbar-track{background:
|
|
119
|
+
::-webkit-scrollbar-track{background:transparent}
|
|
113
120
|
::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
|
|
114
|
-
::-webkit-scrollbar-thumb:hover{background:var(--
|
|
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(
|
|
118
|
-
.msg-bubble
|
|
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(--
|
|
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
|
|
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(--
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
999
|
-
if (!
|
|
1000
|
-
if (!
|
|
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
|