weflow-cli 1.6.4 → 1.8.0
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/ARCHITECTURE.md +3 -2
- package/CHANGELOG.md +1966 -0
- package/OPERATIONS.md +808 -0
- package/README.en.md +93 -7
- package/README.md +31 -6
- package/bin/weflow-cli.ts +1245 -26
- package/dist/bin/weflow-cli.js +1349 -72
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/mcp-server/index.js +39 -13
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/core/appMsgFormat.d.ts +37 -0
- package/dist/src/core/appMsgFormat.d.ts.map +1 -0
- package/dist/src/core/appMsgFormat.js +106 -0
- package/dist/src/core/appMsgFormat.js.map +1 -0
- package/dist/src/core/ntCore.d.ts +43 -0
- package/dist/src/core/ntCore.d.ts.map +1 -1
- package/dist/src/core/ntCore.js +31 -0
- package/dist/src/core/ntCore.js.map +1 -1
- package/dist/src/core/sqlcipherCore.d.ts +0 -12
- package/dist/src/core/sqlcipherCore.d.ts.map +1 -1
- package/dist/src/core/sqlcipherCore.js +5 -64
- package/dist/src/core/sqlcipherCore.js.map +1 -1
- package/dist/src/core/wcdbCore.d.ts +1 -1
- package/dist/src/core/wcdbCore.d.ts.map +1 -1
- package/dist/src/core/wcdbCore.js +5 -1
- package/dist/src/core/wcdbCore.js.map +1 -1
- package/dist/src/panel/client.d.ts +25 -0
- package/dist/src/panel/client.d.ts.map +1 -0
- package/dist/src/panel/client.js +34 -0
- package/dist/src/panel/client.js.map +1 -0
- package/dist/src/panel/endpoint.d.ts +45 -0
- package/dist/src/panel/endpoint.d.ts.map +1 -0
- package/dist/src/panel/endpoint.js +132 -0
- package/dist/src/panel/endpoint.js.map +1 -0
- package/dist/src/panel/launch.d.ts +50 -0
- package/dist/src/panel/launch.d.ts.map +1 -0
- package/dist/src/panel/launch.js +74 -0
- package/dist/src/panel/launch.js.map +1 -0
- package/dist/src/panel/server.d.ts +23 -0
- package/dist/src/panel/server.d.ts.map +1 -0
- package/dist/src/panel/server.js +413 -0
- package/dist/src/panel/server.js.map +1 -0
- package/dist/src/panel/userId.d.ts +37 -0
- package/dist/src/panel/userId.d.ts.map +1 -0
- package/dist/src/panel/userId.js +56 -0
- package/dist/src/panel/userId.js.map +1 -0
- package/dist/src/services/assistantDaemon.d.ts +20 -3
- package/dist/src/services/assistantDaemon.d.ts.map +1 -1
- package/dist/src/services/assistantDaemon.js +46 -9
- package/dist/src/services/assistantDaemon.js.map +1 -1
- package/dist/src/services/assistantEval.d.ts +141 -0
- package/dist/src/services/assistantEval.d.ts.map +1 -0
- package/dist/src/services/assistantEval.js +497 -0
- package/dist/src/services/assistantEval.js.map +1 -0
- package/dist/src/services/assistantMemory.d.ts +106 -4
- package/dist/src/services/assistantMemory.d.ts.map +1 -1
- package/dist/src/services/assistantMemory.js +396 -61
- package/dist/src/services/assistantMemory.js.map +1 -1
- package/dist/src/services/assistantPrivacy.d.ts +15 -2
- package/dist/src/services/assistantPrivacy.d.ts.map +1 -1
- package/dist/src/services/assistantPrivacy.js +18 -4
- package/dist/src/services/assistantPrivacy.js.map +1 -1
- package/dist/src/services/assistantRouter.d.ts +41 -0
- package/dist/src/services/assistantRouter.d.ts.map +1 -0
- package/dist/src/services/assistantRouter.js +151 -0
- package/dist/src/services/assistantRouter.js.map +1 -0
- package/dist/src/services/assistantService.d.ts +209 -3
- package/dist/src/services/assistantService.d.ts.map +1 -1
- package/dist/src/services/assistantService.js +590 -75
- package/dist/src/services/assistantService.js.map +1 -1
- package/dist/src/services/assistantTools.d.ts +205 -1
- package/dist/src/services/assistantTools.d.ts.map +1 -1
- package/dist/src/services/assistantTools.js +1089 -58
- package/dist/src/services/assistantTools.js.map +1 -1
- package/dist/src/services/assistantTrace.d.ts +47 -0
- package/dist/src/services/assistantTrace.d.ts.map +1 -0
- package/dist/src/services/assistantTrace.js +150 -0
- package/dist/src/services/assistantTrace.js.map +1 -0
- package/dist/src/services/chatService.d.ts +13 -0
- package/dist/src/services/chatService.d.ts.map +1 -1
- package/dist/src/services/chatService.js +23 -0
- package/dist/src/services/chatService.js.map +1 -1
- package/dist/src/services/configService.d.ts +9 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +15 -8
- package/dist/src/services/configService.js.map +1 -1
- package/dist/src/services/messageQuery.d.ts +35 -0
- package/dist/src/services/messageQuery.d.ts.map +1 -1
- package/dist/src/services/messageQuery.js +61 -0
- package/dist/src/services/messageQuery.js.map +1 -1
- package/dist/src/services/pythonBridge.d.ts +41 -0
- package/dist/src/services/pythonBridge.d.ts.map +1 -0
- package/dist/src/services/pythonBridge.js +119 -0
- package/dist/src/services/pythonBridge.js.map +1 -0
- package/dist/src/services/syncService.d.ts +127 -0
- package/dist/src/services/syncService.d.ts.map +1 -0
- package/dist/src/services/syncService.js +263 -0
- package/dist/src/services/syncService.js.map +1 -0
- package/dist/src/services/syncState.d.ts +113 -0
- package/dist/src/services/syncState.d.ts.map +1 -0
- package/dist/src/services/syncState.js +214 -0
- package/dist/src/services/syncState.js.map +1 -0
- package/dist/src/services/wechatMessageService.d.ts +25 -0
- package/dist/src/services/wechatMessageService.d.ts.map +1 -1
- package/dist/src/services/wechatMessageService.js +69 -7
- package/dist/src/services/wechatMessageService.js.map +1 -1
- package/dist/src/utils/atomicWrite.d.ts +4 -0
- package/dist/src/utils/atomicWrite.d.ts.map +1 -0
- package/dist/src/utils/atomicWrite.js +60 -0
- package/dist/src/utils/atomicWrite.js.map +1 -0
- package/dist/src/utils/dateRange.d.ts +15 -0
- package/dist/src/utils/dateRange.d.ts.map +1 -1
- package/dist/src/utils/dateRange.js +47 -0
- package/dist/src/utils/dateRange.js.map +1 -1
- package/dist/src/utils/text.d.ts +18 -0
- package/dist/src/utils/text.d.ts.map +1 -0
- package/dist/src/utils/text.js +20 -0
- package/dist/src/utils/text.js.map +1 -0
- package/docs/AI_INTERFACE.md +2 -0
- package/docs/DECISIONS.md +1484 -20
- package/docs/EXTENDING.md +187 -0
- package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +338 -0
- package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +310 -0
- package/docs/MCP.md +23 -4
- package/docs/PROJECT_STATE.md +299 -96
- package/docs/SETUP.md +22 -0
- package/docs/SYNC_CONTRACT.md +180 -0
- package/mcp-server/index.ts +681 -657
- package/package.json +84 -83
- package/resources/panel/ball-position.cjs +153 -0
- package/resources/panel/index.html +46 -0
- package/resources/panel/main.cjs +447 -0
- package/resources/panel/mascot-focus.png +0 -0
- package/resources/panel/mascot-happy.png +0 -0
- package/resources/panel/mascot-sorry.png +0 -0
- package/resources/panel/mascot-tired.png +0 -0
- package/resources/panel/mascot.png +0 -0
- package/resources/panel/package.json +7 -0
- package/resources/panel/panel.css +407 -0
- package/resources/panel/preload.cjs +72 -0
- package/resources/panel/quick-menu.cjs +197 -0
- package/resources/panel/renderer.js +441 -0
- package/resources/panel/tray-menu.cjs +40 -0
- package/resources/panel/tray.png +0 -0
- package/scripts/_utils.py +403 -54
- package/scripts/annual_report.py +4 -2
- package/scripts/article_notes.py +423 -0
- package/scripts/backfill_articles.py +438 -0
- package/scripts/biz_daily.py +590 -105
- package/scripts/chat_notes.py +470 -0
- package/scripts/classify_daily.py +10 -18
- package/scripts/compile_wiki.py +1059 -46
- package/scripts/create_reading_notes.py +199 -14
- package/scripts/decide.py +253 -0
- package/scripts/draft_reply.py +671 -0
- package/scripts/export_chat_html.py +212 -46
- package/scripts/extract_todos.py +31 -5
- package/scripts/fav_notes.py +318 -0
- package/scripts/fix_topics.py +2 -1
- package/scripts/generate_ai_report.py +214 -9
- package/scripts/generate_html.py +18 -3
- package/scripts/generate_review.py +2 -2
- package/scripts/jev_client.py +294 -0
- package/scripts/jev_probe.py +440 -0
- package/scripts/nt_common.py +82 -0
- package/scripts/nt_decrypt.py +1755 -1352
- package/scripts/pipeline.py +27 -2
- package/scripts/promote_ideas.py +19 -4
- package/scripts/quality_eval.py +707 -0
- package/scripts/rag_chat.py +4 -4
- package/scripts/read_image.py +187 -0
- package/scripts/reply_debt.py +488 -0
- package/scripts/route_cards.py +453 -0
- package/scripts/semantic_search.py +208 -49
- package/scripts/user_notes.py +315 -0
- package/scripts/vault_rag.py +13 -7
- package/scripts/vault_search.py +21 -4
- package/scripts/wiki_lint.py +397 -0
- package/src/core/appMsgFormat.ts +117 -0
- package/src/core/ntCore.ts +67 -0
- package/src/core/sqlcipherCore.ts +5 -61
- package/src/core/wcdbCore.ts +6 -2
- package/src/panel/client.ts +49 -0
- package/src/panel/endpoint.ts +144 -0
- package/src/panel/launch.ts +101 -0
- package/src/panel/server.ts +424 -0
- package/src/panel/userId.ts +72 -0
- package/src/services/assistantDaemon.ts +57 -9
- package/src/services/assistantEval.ts +611 -0
- package/src/services/assistantMemory.ts +413 -57
- package/src/services/assistantPrivacy.ts +18 -4
- package/src/services/assistantRouter.ts +191 -0
- package/src/services/assistantService.ts +671 -74
- package/src/services/assistantTools.ts +1118 -57
- package/src/services/assistantTrace.ts +175 -0
- package/src/services/chatService.ts +30 -0
- package/src/services/configService.ts +403 -385
- package/src/services/messageQuery.ts +97 -0
- package/src/services/pythonBridge.ts +139 -0
- package/src/services/syncService.ts +363 -0
- package/src/services/syncState.ts +275 -0
- package/src/services/wechatMessageService.ts +73 -7
- package/src/utils/atomicWrite.ts +56 -0
- package/src/utils/dateRange.ts +46 -0
- package/src/utils/text.ts +20 -0
package/package.json
CHANGED
|
@@ -1,83 +1,84 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "weflow-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "让 AI 读懂你的微信 — 公众号日报·聊天导出·知识库搜索·MCP Server",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"weflow-cli": "./cli.cjs"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"cli.cjs",
|
|
11
|
-
"dist/",
|
|
12
|
-
"scripts/**/*.py",
|
|
13
|
-
"mcp-server/",
|
|
14
|
-
"bin/",
|
|
15
|
-
"src/",
|
|
16
|
-
"resources/",
|
|
17
|
-
"docs/images/",
|
|
18
|
-
"docs/*.md",
|
|
19
|
-
"README.md",
|
|
20
|
-
"OPERATIONS.md",
|
|
21
|
-
"ARCHITECTURE.md",
|
|
22
|
-
"CONTRIBUTING.md",
|
|
23
|
-
"SECURITY.md",
|
|
24
|
-
"CHANGELOG.md",
|
|
25
|
-
"requirements*.txt",
|
|
26
|
-
"LICENSE"
|
|
27
|
-
],
|
|
28
|
-
"keywords": [
|
|
29
|
-
"wechat",
|
|
30
|
-
"weixin",
|
|
31
|
-
"mcp",
|
|
32
|
-
"rss",
|
|
33
|
-
"daily",
|
|
34
|
-
"公众号",
|
|
35
|
-
"聊天记录",
|
|
36
|
-
"ai",
|
|
37
|
-
"deepseek",
|
|
38
|
-
"claude"
|
|
39
|
-
],
|
|
40
|
-
"repository": {
|
|
41
|
-
"type": "git",
|
|
42
|
-
"url": "https://github.com/zhuobichen/weflow-cli"
|
|
43
|
-
},
|
|
44
|
-
"scripts": {
|
|
45
|
-
"build": "tsc",
|
|
46
|
-
"test": "node --import tsx --test \"test/**/*.test.ts\"",
|
|
47
|
-
"dev": "npx tsx bin/weflow-cli.ts",
|
|
48
|
-
"start": "node cli.cjs",
|
|
49
|
-
"prepublishOnly": "npm run build"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"@
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"marked
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"@types/
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "weflow-cli",
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"description": "让 AI 读懂你的微信 — 公众号日报·聊天导出·知识库搜索·MCP Server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"weflow-cli": "./cli.cjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli.cjs",
|
|
11
|
+
"dist/",
|
|
12
|
+
"scripts/**/*.py",
|
|
13
|
+
"mcp-server/",
|
|
14
|
+
"bin/",
|
|
15
|
+
"src/",
|
|
16
|
+
"resources/",
|
|
17
|
+
"docs/images/",
|
|
18
|
+
"docs/*.md",
|
|
19
|
+
"README.md",
|
|
20
|
+
"OPERATIONS.md",
|
|
21
|
+
"ARCHITECTURE.md",
|
|
22
|
+
"CONTRIBUTING.md",
|
|
23
|
+
"SECURITY.md",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"requirements*.txt",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"wechat",
|
|
30
|
+
"weixin",
|
|
31
|
+
"mcp",
|
|
32
|
+
"rss",
|
|
33
|
+
"daily",
|
|
34
|
+
"公众号",
|
|
35
|
+
"聊天记录",
|
|
36
|
+
"ai",
|
|
37
|
+
"deepseek",
|
|
38
|
+
"claude"
|
|
39
|
+
],
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/zhuobichen/weflow-cli"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc",
|
|
46
|
+
"test": "node --import tsx --test \"test/**/*.test.ts\"",
|
|
47
|
+
"dev": "npx tsx bin/weflow-cli.ts",
|
|
48
|
+
"start": "node cli.cjs",
|
|
49
|
+
"prepublishOnly": "npm run build",
|
|
50
|
+
"eval:assistant": "node --import tsx scripts/assistant_eval.ts"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
54
|
+
"@wenyan-md/core": "^3.0.10",
|
|
55
|
+
"chalk": "^5.4.1",
|
|
56
|
+
"commander": "^13.1.0",
|
|
57
|
+
"css-tree": "^3.2.1",
|
|
58
|
+
"exceljs": "^4.4.0",
|
|
59
|
+
"form-data-encoder": "^4.1.0",
|
|
60
|
+
"formdata-node": "^6.0.3",
|
|
61
|
+
"front-matter": "^4.0.2",
|
|
62
|
+
"fzstd": "^0.1.1",
|
|
63
|
+
"highlight.js": "^11.11.1",
|
|
64
|
+
"inquirer": "^13.4.3",
|
|
65
|
+
"jsdom": "^27.4.0",
|
|
66
|
+
"koffi": "^2.9.0",
|
|
67
|
+
"marked": "^18.0.4",
|
|
68
|
+
"marked-highlight": "^2.2.4",
|
|
69
|
+
"qrcode-terminal": "^0.12.0"
|
|
70
|
+
},
|
|
71
|
+
"optionalDependencies": {
|
|
72
|
+
"lz4": "^0.6.5"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@types/node": "^22.0.0",
|
|
76
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
77
|
+
"electron": "^42.0.1",
|
|
78
|
+
"tsx": "^4.19.0",
|
|
79
|
+
"typescript": "^5.7.0"
|
|
80
|
+
},
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=22.13.0"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 小球该在哪儿:**纯算术**,不 require electron。
|
|
3
|
+
*
|
|
4
|
+
* 为什么单独一个文件:多显示器这件事**在这台机器上造不出来**(只有一块屏)。
|
|
5
|
+
* 但真正要保证的是一条数学性质——"记住的位置永远落得回某块屏的可见区域里"——
|
|
6
|
+
* 它可以在 CI 里用**合成的显示器布局**验:左副屏的 x 是负的、副屏被拔掉后位置不可达、
|
|
7
|
+
* 球比工作区还大……这些都是这台机器上永远试不到、却一定会发生在别人机器上的情况。
|
|
8
|
+
*
|
|
9
|
+
* `main.cjs` 负责"问 Electron 要显示器列表"(`screen.getAllDisplays()`),
|
|
10
|
+
* 这里只负责算。两边分开之后,算的那半有测试,问的那半是 Electron 的。
|
|
11
|
+
*
|
|
12
|
+
* 坐标是 Electron 的坐标:多屏时左副屏的 `x` 是**负数**,主屏的 `workArea` 也扣掉了任务栏。
|
|
13
|
+
*/
|
|
14
|
+
'use strict'
|
|
15
|
+
|
|
16
|
+
const BALL_SIZE = 96
|
|
17
|
+
/** 球离屏幕边缘留多少:贴死边缘在 Windows 上会跟任务栏/贴边功能打架 */
|
|
18
|
+
const EDGE_MARGIN = 24
|
|
19
|
+
/** 对话气泡多大(展开后那块圆角面板)。`panel.css` 里有一份 `--bubble-width`,有测试钉住两者相等。 */
|
|
20
|
+
const BUBBLE_SIZE = { width: 420, height: 560 }
|
|
21
|
+
/** 气泡与球之间留的空隙。气泡那侧的透明缝隙靠它,`--bubble-gap` 是同一个数。 */
|
|
22
|
+
const BUBBLE_GAP = 12
|
|
23
|
+
|
|
24
|
+
/** 默认位置:主屏工作区的右下角 */
|
|
25
|
+
function defaultBallPosition(workArea, ballSize = BALL_SIZE, margin = EDGE_MARGIN) {
|
|
26
|
+
return {
|
|
27
|
+
x: workArea.x + workArea.width - ballSize - margin,
|
|
28
|
+
y: workArea.y + workArea.height - ballSize - margin,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 把一个 `size x size` 的方块挪进给定工作区(只挪,不改尺寸) */
|
|
33
|
+
function clampInto(x, y, size, workArea) {
|
|
34
|
+
const maxX = workArea.x + workArea.width - size
|
|
35
|
+
const maxY = workArea.y + workArea.height - size
|
|
36
|
+
return {
|
|
37
|
+
x: Math.max(workArea.x, Math.min(x, maxX)),
|
|
38
|
+
y: Math.max(workArea.y, Math.min(y, maxY)),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 记住的位置还能不能用:判据取**球的中心点**落在某块工作区里。
|
|
44
|
+
* 中心可见就一定抓得回来——只露 1 像素也算"可见",但那不叫抓得回来。
|
|
45
|
+
*/
|
|
46
|
+
function isReachable(pos, workAreas, ballSize = BALL_SIZE) {
|
|
47
|
+
const cx = pos.x + ballSize / 2
|
|
48
|
+
const cy = pos.y + ballSize / 2
|
|
49
|
+
return workAreas.some((a) =>
|
|
50
|
+
cx >= a.x && cx <= a.x + a.width && cy >= a.y && cy <= a.y + a.height)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 启动时该把球放哪:记住的位置可达就用它,否则回默认角落。
|
|
55
|
+
* **副屏被拔掉、或用户把球拖到屏幕外,走的就是这一条**。
|
|
56
|
+
*/
|
|
57
|
+
function resolveStartPosition(savedPos, workAreas, primaryWorkArea, ballSize = BALL_SIZE, margin = EDGE_MARGIN) {
|
|
58
|
+
if (savedPos && Number.isInteger(savedPos.x) && Number.isInteger(savedPos.y)
|
|
59
|
+
&& isReachable(savedPos, workAreas, ballSize)) {
|
|
60
|
+
return { x: savedPos.x, y: savedPos.y }
|
|
61
|
+
}
|
|
62
|
+
return defaultBallPosition(primaryWorkArea, ballSize, margin)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 展开时的整体布局:**球 + 空隙 + 气泡**合成一个矩形,球在它那个角上**一动不动**。
|
|
67
|
+
*
|
|
68
|
+
* 用户要的是"图标别撤起来,气泡在旁边长出来,像这个图标在说话"。这决定了三件事:
|
|
69
|
+
*
|
|
70
|
+
* 1. 展开后的窗口比气泡大一圈——多出来的就是球占的那条(外加空隙),窗口宽
|
|
71
|
+
* = 420 + 12 + 96 = 528;
|
|
72
|
+
* 2. **球必须正好坐在窗口的某个角上**。这是硬要求,不是审美:窗口是一整块,页面把球
|
|
73
|
+
* 钉在角上(`position: fixed; right/bottom`),于是**窗口怎么改大小,球在屏幕上的位置
|
|
74
|
+
* 都不变**——因为那个角没动。反过来,球要是不在角上(改成按坐标钉),窗口任意时刻改
|
|
75
|
+
* 大小,球都会先跟着窗口跳一下、等页面收到新坐标才回来,那就是一闪。
|
|
76
|
+
* 3. 气泡默认开在球的**左边**(球默认在右下角,往左上长不遮屏),左边放不下就翻到右边。
|
|
77
|
+
*
|
|
78
|
+
* **竖向:气泡的高度可缩,球的位置不可动。** 先算两种对齐各自能有多高——
|
|
79
|
+
* 底对齐(球的底 = 气泡的底)时 `球底 - 工作区顶`,顶对齐时 `工作区底 - 球顶`——
|
|
80
|
+
* **取高的那个**(够 560 就用 560),气泡高度随之为那个值。于是:
|
|
81
|
+
*
|
|
82
|
+
* - 球在下面(默认)→ 底对齐给满 560,球在窗口**右下角**;
|
|
83
|
+
* - 球在中间(本机实测用户把球拖到了 967,302)→ 底对齐只有 378,顶对齐有 560 → 顶对齐,
|
|
84
|
+
* 球在窗口**右上角**,气泡整块挂在它下面;
|
|
85
|
+
* - 两头都不够高(很小的屏幕)→ 取高的那个,气泡就是矮一点,不越界。
|
|
86
|
+
*
|
|
87
|
+
* 从前那版是"气泡固定 560 高、上下夹取",结果球在中间时窗口被夹进工作区、**球跟着挪了
|
|
88
|
+
* 57-75px**(用户屏幕上就是球跳了一下)。让气泡变矮就再也不用挪球。
|
|
89
|
+
*
|
|
90
|
+
* @returns {{window: {x,y,width,height}, side: 'left'|'right', anchorY: 'bottom'|'top', bubbleHeight: number}}
|
|
91
|
+
*/
|
|
92
|
+
function bubbleLayout(ball, bubble = BUBBLE_SIZE, workArea, gap = BUBBLE_GAP, ballSize = BALL_SIZE) {
|
|
93
|
+
const leftX = ball.x - gap - bubble.width
|
|
94
|
+
const rightX = ball.x + ballSize + gap
|
|
95
|
+
const fitsLeft = leftX >= workArea.x
|
|
96
|
+
const fitsRight = rightX + bubble.width <= workArea.x + workArea.width
|
|
97
|
+
|
|
98
|
+
let side = 'left'
|
|
99
|
+
let bubbleX = leftX
|
|
100
|
+
if (!fitsLeft && fitsRight) {
|
|
101
|
+
side = 'right'
|
|
102
|
+
bubbleX = rightX
|
|
103
|
+
} else if (!fitsLeft && !fitsRight) {
|
|
104
|
+
const roomLeft = ball.x - workArea.x
|
|
105
|
+
const roomRight = (workArea.x + workArea.width) - (ball.x + ballSize)
|
|
106
|
+
side = roomLeft >= roomRight ? 'left' : 'right'
|
|
107
|
+
bubbleX = side === 'left' ? leftX : rightX
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 竖向:两种对齐各能有多高,取高的那个
|
|
111
|
+
const heightIfBottom = Math.min(bubble.height, (ball.y + ballSize) - workArea.y)
|
|
112
|
+
const heightIfTop = Math.min(bubble.height, (workArea.y + workArea.height) - ball.y)
|
|
113
|
+
const anchorY = heightIfTop > heightIfBottom ? 'top' : 'bottom'
|
|
114
|
+
const bubbleHeight = Math.max(1, anchorY === 'top' ? heightIfTop : heightIfBottom)
|
|
115
|
+
const bubbleY = anchorY === 'top' ? ball.y : ball.y + ballSize - bubbleHeight
|
|
116
|
+
|
|
117
|
+
// 窗口 = 球与气泡的并集(球在角上,所以并集就是"气泡 + 空隙 + 球"那一块)
|
|
118
|
+
const x = Math.min(bubbleX, ball.x)
|
|
119
|
+
const y = Math.min(bubbleY, ball.y)
|
|
120
|
+
const window_ = {
|
|
121
|
+
x,
|
|
122
|
+
y,
|
|
123
|
+
width: Math.max(bubbleX + bubble.width, ball.x + ballSize) - x,
|
|
124
|
+
height: Math.max(bubbleY + bubbleHeight, ball.y + ballSize) - y,
|
|
125
|
+
}
|
|
126
|
+
// 横向超出工作区时按 `clampInto` 那套夹(左侧对齐、右侧被裁)——窄屏才走得到
|
|
127
|
+
const maxX = workArea.x + workArea.width - window_.width
|
|
128
|
+
window_.x = Math.max(workArea.x, Math.min(window_.x, maxX))
|
|
129
|
+
|
|
130
|
+
return { window: window_, side, anchorY, bubbleHeight }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 球此刻在屏幕上的矩形:从**当前**窗口矩形与展开时记下的锚(哪一侧、哪个角)反推。
|
|
135
|
+
*
|
|
136
|
+
* 收起时要用——用户拖着标题栏挪过窗口、或者拉大过它,球都跟着那个角走,所以反推始终成立。
|
|
137
|
+
* **不能图省事拿窗口的左上角当球的落点**:球在别的角上,那样收起时球会跳到气泡的另一头去
|
|
138
|
+
* (上一版真的这么写的,探针量出来的)。
|
|
139
|
+
*/
|
|
140
|
+
function ballRectInWindow(windowRect, anchor, ballSize = BALL_SIZE) {
|
|
141
|
+
const atRight = anchor.side === 'left' // 气泡在左 → 球在窗口右
|
|
142
|
+
const atBottom = anchor.anchorY !== 'top'
|
|
143
|
+
return {
|
|
144
|
+
x: atRight ? windowRect.x + windowRect.width - ballSize : windowRect.x,
|
|
145
|
+
y: atBottom ? windowRect.y + windowRect.height - ballSize : windowRect.y,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
BALL_SIZE, EDGE_MARGIN, BUBBLE_SIZE, BUBBLE_GAP,
|
|
151
|
+
defaultBallPosition, clampInto, isReachable, resolveStartPosition, bubbleLayout,
|
|
152
|
+
ballRectInWindow,
|
|
153
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>第二大脑</title>
|
|
6
|
+
<link rel="stylesheet" href="/panel/panel.css">
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<!-- 悬浮球。球面是 resources/panel/mascot.png(按内容包围盒裁过的吉祥物),
|
|
10
|
+
由守护进程按白名单递过来;只在**有外壳**(Electron)时才显示,靠 renderer 加的
|
|
11
|
+
`body.shell` 这个类——浏览器那条路给不了无边框置顶的小球,硬做只会做出一个
|
|
12
|
+
奇怪的方窗口,所以那边永远看不见球(`hidden` 属性与 CSS 里那条 `display:none`
|
|
13
|
+
都是为它留的)。 -->
|
|
14
|
+
<button id="ball" type="button" title="第二大脑(点击展开 · 可拖动)" hidden>
|
|
15
|
+
<!-- 两层:底下一层是会动的光晕(背景),上面一层是吉祥物。
|
|
16
|
+
分开是为了让光晕能变而猫不动——直接给 #ball 设 background 的话,
|
|
17
|
+
光晕与猫共用同一层,动不了其中一个。 -->
|
|
18
|
+
<span class="glow" aria-hidden="true"></span>
|
|
19
|
+
<span class="face" aria-hidden="true"></span>
|
|
20
|
+
</button>
|
|
21
|
+
|
|
22
|
+
<!-- 对话气泡。**单独一层**:它要有自己的圆角与底色,而展开时窗口比它大一圈
|
|
23
|
+
(多出来的是球占的那条 + 空隙),那一圈必须是**透明**的——所以底色不能画在 body 上。
|
|
24
|
+
尾巴是它的子节点,这样补间过程中自动跟着气泡走,还没露出来时被窗口裁掉即可。 -->
|
|
25
|
+
<div id="bubble">
|
|
26
|
+
<header>
|
|
27
|
+
<span class="brand">第二大脑</span>
|
|
28
|
+
<span id="status" class="status">连接中…</span>
|
|
29
|
+
<button id="collapse" type="button" class="ghost" hidden>收起</button>
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
<main id="log" aria-live="polite"></main>
|
|
33
|
+
|
|
34
|
+
<form id="composer" autocomplete="off">
|
|
35
|
+
<textarea id="input" rows="2" placeholder="问点什么…(Enter 发送,Shift+Enter 换行)"></textarea>
|
|
36
|
+
<button id="send" type="submit">发送</button>
|
|
37
|
+
</form>
|
|
38
|
+
|
|
39
|
+
<footer id="foot"></footer>
|
|
40
|
+
|
|
41
|
+
<div id="tail" aria-hidden="true"></div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<script src="/panel/renderer.js"></script>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|