mioku-plugin-help 2.0.0 → 2.1.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/help/html-generator.ts +556 -0
- package/help/image.ts +216 -0
- package/help/index.ts +24 -0
- package/help/info.ts +41 -0
- package/help/intent.ts +379 -0
- package/help/role-config.ts +67 -0
- package/help/types.ts +40 -0
- package/index.ts +69 -11
- package/package.json +4 -3
- package/skills.ts +132 -77
- package/status/data-collector.ts +752 -0
- package/status/html-generator.ts +1202 -0
- package/status/image.ts +81 -0
- package/status/index.ts +28 -0
- package/status/intent.ts +43 -0
- package/status/network-sampler.ts +129 -0
- package/status/performance-monitor.ts +102 -0
- package/status/types.ts +264 -0
- package/theme.ts +138 -0
- package/utils.ts +129 -0
- package/shared.ts +0 -1276
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role badge palette and keyword stop-words.
|
|
3
|
+
*
|
|
4
|
+
* Both are referenced from multiple files in the help flow (intent
|
|
5
|
+
* matching, HTML rendering), so we keep them in one place to avoid
|
|
6
|
+
* divergent definitions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { CommandRole } from "mioku";
|
|
10
|
+
|
|
11
|
+
/** Per-role label + light/dark badge colors. */
|
|
12
|
+
export const ROLE_CONFIG: Record<
|
|
13
|
+
CommandRole,
|
|
14
|
+
{
|
|
15
|
+
label: string;
|
|
16
|
+
badgeBgLight: string;
|
|
17
|
+
badgeBorderLight: string;
|
|
18
|
+
badgeTextLight: string;
|
|
19
|
+
badgeBgDark: string;
|
|
20
|
+
badgeBorderDark: string;
|
|
21
|
+
badgeTextDark: string;
|
|
22
|
+
}
|
|
23
|
+
> = {
|
|
24
|
+
master: {
|
|
25
|
+
label: "主人",
|
|
26
|
+
badgeBgLight: "rgba(245, 158, 11, 0.14)",
|
|
27
|
+
badgeBorderLight: "rgba(217, 119, 6, 0.24)",
|
|
28
|
+
badgeTextLight: "#92400e",
|
|
29
|
+
badgeBgDark: "rgba(245, 158, 11, 0.18)",
|
|
30
|
+
badgeBorderDark: "rgba(251, 191, 36, 0.28)",
|
|
31
|
+
badgeTextDark: "#fcd34d",
|
|
32
|
+
},
|
|
33
|
+
admin: {
|
|
34
|
+
label: "管理",
|
|
35
|
+
badgeBgLight: "rgba(239, 68, 68, 0.12)",
|
|
36
|
+
badgeBorderLight: "rgba(220, 38, 38, 0.22)",
|
|
37
|
+
badgeTextLight: "#b91c1c",
|
|
38
|
+
badgeBgDark: "rgba(239, 68, 68, 0.16)",
|
|
39
|
+
badgeBorderDark: "rgba(248, 113, 113, 0.22)",
|
|
40
|
+
badgeTextDark: "#fca5a5",
|
|
41
|
+
},
|
|
42
|
+
owner: {
|
|
43
|
+
label: "管理",
|
|
44
|
+
badgeBgLight: "rgba(239, 68, 68, 0.12)",
|
|
45
|
+
badgeBorderLight: "rgba(220, 38, 38, 0.22)",
|
|
46
|
+
badgeTextLight: "#b91c1c",
|
|
47
|
+
badgeBgDark: "rgba(239, 68, 68, 0.16)",
|
|
48
|
+
badgeBorderDark: "rgba(248, 113, 113, 0.22)",
|
|
49
|
+
badgeTextDark: "#fca5a5",
|
|
50
|
+
},
|
|
51
|
+
member: {
|
|
52
|
+
label: "成员",
|
|
53
|
+
badgeBgLight: "rgba(14, 165, 233, 0.12)",
|
|
54
|
+
badgeBorderLight: "rgba(2, 132, 199, 0.2)",
|
|
55
|
+
badgeTextLight: "#0369a1",
|
|
56
|
+
badgeBgDark: "rgba(56, 189, 248, 0.16)",
|
|
57
|
+
badgeBorderDark: "rgba(103, 232, 249, 0.22)",
|
|
58
|
+
badgeTextDark: "#67e8f9",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Words that should never be treated as a plugin name, even if the user
|
|
64
|
+
* typed something like "help 帮助". Used by both intent matching and
|
|
65
|
+
* alias extraction.
|
|
66
|
+
*/
|
|
67
|
+
export const STOPWORDS = new Set(["help", "帮助", "菜单"]);
|
package/help/types.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the help image flow.
|
|
3
|
+
*
|
|
4
|
+
* Kept in their own module so `intent.ts`, `info.ts`, `html-generator.ts`,
|
|
5
|
+
* and consumers can all import from a single place without creating cycles.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PluginHelp } from "mioku";
|
|
9
|
+
|
|
10
|
+
/** Result of parsing a chat message into a help-image action. */
|
|
11
|
+
export type HelpImageIntent =
|
|
12
|
+
| { type: "none" }
|
|
13
|
+
| { type: "overview" }
|
|
14
|
+
| { type: "detail"; pluginName: string; pluginHelp: PluginHelp }
|
|
15
|
+
| { type: "unknown"; keyword: string };
|
|
16
|
+
|
|
17
|
+
/** Internal record used while ranking fuzzy keyword matches. */
|
|
18
|
+
export interface HelpRenderableEntry {
|
|
19
|
+
pluginName: string;
|
|
20
|
+
title: string;
|
|
21
|
+
description: string;
|
|
22
|
+
commands: PluginHelp["commands"];
|
|
23
|
+
/** Lower-cased, alphanumeric-only form of `pluginName` for matching. */
|
|
24
|
+
normalizedPluginName: string;
|
|
25
|
+
/** Lower-cased, alphanumeric-only form of `title` for matching. */
|
|
26
|
+
normalizedTitle: string;
|
|
27
|
+
/** All normalized tokens we treat as equivalent to this entry. */
|
|
28
|
+
matchKeys: Set<string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A candidate plugin keyword extracted from a chat message. */
|
|
32
|
+
export interface HelpKeywordCandidate {
|
|
33
|
+
keyword: string;
|
|
34
|
+
/**
|
|
35
|
+
* `true` if the keyword came from a strict help prefix/suffix pattern
|
|
36
|
+
* (e.g. "插件名 帮助"). A strict unknown should be surfaced to the user
|
|
37
|
+
* as "没有找到插件 X 的帮助"; loose matches silently fall through.
|
|
38
|
+
*/
|
|
39
|
+
strictUnknown: boolean;
|
|
40
|
+
}
|
package/index.ts
CHANGED
|
@@ -6,19 +6,29 @@ import * as path from "path";
|
|
|
6
6
|
import { HELP_DEMO_CONFIG } from "./demo-config";
|
|
7
7
|
import {
|
|
8
8
|
generateHelpImage,
|
|
9
|
-
getPackageVersion,
|
|
10
9
|
replyWithImage,
|
|
11
10
|
resolveHelpBotProfile,
|
|
12
11
|
resolveHelpImageIntent,
|
|
13
|
-
} from "./
|
|
12
|
+
} from "./help";
|
|
13
|
+
import { getRenderVersions } from "./utils";
|
|
14
14
|
import { resetHelpRuntimeState, setHelpRuntimeState } from "./runtime";
|
|
15
|
+
import {
|
|
16
|
+
generateStatusImage,
|
|
17
|
+
networkSampler,
|
|
18
|
+
perfMonitor,
|
|
19
|
+
resolveStatusIntent,
|
|
20
|
+
} from "./status";
|
|
15
21
|
|
|
16
22
|
const helpPlugin = definePlugin({
|
|
17
23
|
name: "help",
|
|
18
|
-
version: "1.0
|
|
19
|
-
description: "
|
|
24
|
+
version: "2.1.0",
|
|
25
|
+
description: "帮助插件,生成帮助图片,并提供 #状态 指令",
|
|
20
26
|
|
|
21
27
|
async setup(ctx: MiokiContext) {
|
|
28
|
+
// 启动后台采样器
|
|
29
|
+
networkSampler.start();
|
|
30
|
+
perfMonitor.start();
|
|
31
|
+
|
|
22
32
|
const configService = ctx.services?.config as ConfigService | undefined;
|
|
23
33
|
const helpService = ctx.services?.help as HelpService | undefined;
|
|
24
34
|
const screenshotService = ctx.services?.screenshot as
|
|
@@ -27,7 +37,12 @@ const helpPlugin = definePlugin({
|
|
|
27
37
|
|
|
28
38
|
if (!helpService) {
|
|
29
39
|
ctx.logger.warn("help-service 未加载,帮助插件无法运行");
|
|
30
|
-
return
|
|
40
|
+
return () => {
|
|
41
|
+
networkSampler.stop();
|
|
42
|
+
perfMonitor.stop();
|
|
43
|
+
resetHelpRuntimeState();
|
|
44
|
+
ctx.logger.info("帮助插件已卸载");
|
|
45
|
+
};
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
if (!screenshotService) {
|
|
@@ -38,12 +53,20 @@ const helpPlugin = definePlugin({
|
|
|
38
53
|
await configService.registerConfig("help", "demo", HELP_DEMO_CONFIG.demo);
|
|
39
54
|
}
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
// 注册 help manifest,让 `#help 状态` 能命中
|
|
57
|
+
helpService.registerHelp("help", {
|
|
58
|
+
title: "帮助与系统状态",
|
|
59
|
+
description: "生成帮助图片与系统状态图片",
|
|
60
|
+
commands: [
|
|
61
|
+
{ cmd: "#help", desc: "查看帮助图片" },
|
|
62
|
+
{
|
|
63
|
+
cmd: "#状态",
|
|
64
|
+
desc: "查看完整系统状态图片(账号、资源、网络、磁盘、AI 统计、运行时、系统信息)",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const { miokiVersion, miokuVersion } = await getRenderVersions();
|
|
47
70
|
|
|
48
71
|
setHelpRuntimeState({
|
|
49
72
|
miokiVersion,
|
|
@@ -56,6 +79,39 @@ const helpPlugin = definePlugin({
|
|
|
56
79
|
return;
|
|
57
80
|
}
|
|
58
81
|
|
|
82
|
+
// 状态指令拦截(优先级高于 help 匹配)
|
|
83
|
+
const statusIntent = resolveStatusIntent(text);
|
|
84
|
+
if (statusIntent.type !== "none") {
|
|
85
|
+
if (!screenshotService) {
|
|
86
|
+
await event.reply("screenshot 服务未加载,无法生成状态图片");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const { botNickname, botAvatarUrl } = resolveHelpBotProfile(
|
|
91
|
+
ctx,
|
|
92
|
+
event,
|
|
93
|
+
);
|
|
94
|
+
const result = await generateStatusImage({
|
|
95
|
+
ctx,
|
|
96
|
+
event,
|
|
97
|
+
intent: statusIntent,
|
|
98
|
+
botNickname,
|
|
99
|
+
botAvatarUrl,
|
|
100
|
+
});
|
|
101
|
+
if (result.ok && result.imagePath) {
|
|
102
|
+
await replyWithImage(event, ctx.segment, result.imagePath);
|
|
103
|
+
} else {
|
|
104
|
+
await event.reply(
|
|
105
|
+
`生成状态图片失败: ${result.error || "未知错误"}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
ctx.logger.error(`生成状态图片失败: ${error}`);
|
|
110
|
+
await event.reply(`生成状态图片失败: ${error}`);
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
59
115
|
const allHelp = helpService.getAllHelp();
|
|
60
116
|
const intent = resolveHelpImageIntent(text, allHelp);
|
|
61
117
|
if (intent.type === "none") {
|
|
@@ -98,6 +154,8 @@ const helpPlugin = definePlugin({
|
|
|
98
154
|
});
|
|
99
155
|
|
|
100
156
|
return () => {
|
|
157
|
+
networkSampler.stop();
|
|
158
|
+
perfMonitor.stop();
|
|
101
159
|
resetHelpRuntimeState();
|
|
102
160
|
ctx.logger.info("帮助插件已卸载");
|
|
103
161
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-help",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "帮助插件,使用截图服务生成美观的帮助图片,并提供 #状态 指令",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": [
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"services": [
|
|
16
16
|
"help",
|
|
17
17
|
"screenshot",
|
|
18
|
-
"config"
|
|
18
|
+
"config",
|
|
19
|
+
"ai"
|
|
19
20
|
]
|
|
20
21
|
},
|
|
21
22
|
"peerDependencies": {
|
package/skills.ts
CHANGED
|
@@ -4,91 +4,146 @@ import type { ScreenshotService } from "mioku";
|
|
|
4
4
|
import {
|
|
5
5
|
buildHelpInfoText,
|
|
6
6
|
generateHelpImage,
|
|
7
|
-
getHelpRenderVersions,
|
|
8
7
|
resolveHelpBotProfile,
|
|
9
8
|
sendImageFromSkillContext,
|
|
10
|
-
} from "./
|
|
9
|
+
} from "./help";
|
|
10
|
+
import { getRenderVersions } from "./utils";
|
|
11
|
+
import { generateStatusImage } from "./status";
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
13
|
+
const helpSkill: AISkill = {
|
|
14
|
+
name: "help",
|
|
15
|
+
description: "帮助系统,获取插件帮助信息和发送帮助图片",
|
|
16
|
+
permission: "member",
|
|
17
|
+
tools: [
|
|
18
|
+
{
|
|
19
|
+
name: "get_help_info",
|
|
20
|
+
description:
|
|
21
|
+
"获取所有插件的帮助信息文本,这个仅用于用户向你询问某个功能的具体用法时使用",
|
|
22
|
+
parameters: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {},
|
|
25
|
+
required: [],
|
|
26
|
+
},
|
|
27
|
+
handler: async (_args: any, runtimeCtx?: any) => {
|
|
28
|
+
const ctx = runtimeCtx?.ctx;
|
|
29
|
+
const helpService = ctx?.services?.help as HelpService | undefined;
|
|
30
|
+
if (!helpService) {
|
|
31
|
+
return "help-service 未加载,无法获取帮助信息";
|
|
32
|
+
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
34
|
+
return buildHelpInfoText(helpService.getAllHelp());
|
|
35
|
+
},
|
|
36
|
+
} as AITool,
|
|
37
|
+
{
|
|
38
|
+
name: "send_help_image",
|
|
39
|
+
description:
|
|
40
|
+
"生成并发送帮助图片到群聊,如果有人说他想看帮助,优先调用图片发送而不是自己查看帮助。",
|
|
41
|
+
parameters: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {},
|
|
44
|
+
required: [],
|
|
45
|
+
},
|
|
46
|
+
handler: async (_args: any, runtimeCtx?: any) => {
|
|
47
|
+
const ctx = runtimeCtx?.ctx;
|
|
48
|
+
const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
|
|
49
|
+
const helpService = ctx?.services?.help as HelpService | undefined;
|
|
50
|
+
const screenshotService = ctx?.services?.screenshot as
|
|
51
|
+
| ScreenshotService
|
|
52
|
+
| undefined;
|
|
53
|
+
const { miokiVersion, miokuVersion } = await getRenderVersions();
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
if (!screenshotService) {
|
|
56
|
+
return "screenshot 服务未加载,无法生成帮助图片";
|
|
57
|
+
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
try {
|
|
60
|
+
const { botNickname, botAvatarUrl } = resolveHelpBotProfile(
|
|
61
|
+
ctx,
|
|
62
|
+
event,
|
|
63
|
+
);
|
|
64
|
+
const imagePath = await generateHelpImage({
|
|
65
|
+
helpService,
|
|
66
|
+
screenshotService,
|
|
67
|
+
miokiVersion,
|
|
68
|
+
miokuVersion,
|
|
69
|
+
botNickname,
|
|
70
|
+
botAvatarUrl,
|
|
71
|
+
});
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
if (!imagePath) {
|
|
74
|
+
return "生成帮助图片失败";
|
|
75
|
+
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
77
|
+
// Help skill defaults to normal send instead of quote-reply.
|
|
78
|
+
await sendImageFromSkillContext({
|
|
79
|
+
ctx,
|
|
80
|
+
event,
|
|
81
|
+
imagePath,
|
|
82
|
+
quoteReply: false,
|
|
83
|
+
});
|
|
84
|
+
return "已发送帮助图片";
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return `生成帮助图片失败: ${error}`;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
} as AITool,
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const statusSkill: AISkill = {
|
|
94
|
+
name: "status",
|
|
95
|
+
description:
|
|
96
|
+
"系统状态查询,生成完整的系统状态图片(账号、资源、网络、磁盘、AI 统计、运行时、系统信息)",
|
|
97
|
+
permission: "member",
|
|
98
|
+
tools: [
|
|
99
|
+
{
|
|
100
|
+
name: "send_status_image",
|
|
101
|
+
description:
|
|
102
|
+
"生成并发送完整的系统状态图片。当用户问「看看状态」「机器人怎么样」「服务器忙吗」「资源还够吗」「看看 AI 用了多少」时优先使用。",
|
|
103
|
+
parameters: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {},
|
|
106
|
+
required: [],
|
|
107
|
+
},
|
|
108
|
+
handler: async (_args: any, runtimeCtx?: any) => {
|
|
109
|
+
const ctx = runtimeCtx?.ctx;
|
|
110
|
+
const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
|
|
111
|
+
const screenshotService = ctx?.services?.screenshot as
|
|
112
|
+
| ScreenshotService
|
|
113
|
+
| undefined;
|
|
114
|
+
if (!screenshotService) {
|
|
115
|
+
return "screenshot 服务未加载";
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const { botNickname, botAvatarUrl } = resolveHelpBotProfile(
|
|
119
|
+
ctx,
|
|
120
|
+
event,
|
|
121
|
+
);
|
|
122
|
+
const result = await generateStatusImage({
|
|
123
|
+
ctx,
|
|
124
|
+
event,
|
|
125
|
+
intent: { type: "full" },
|
|
126
|
+
botNickname,
|
|
127
|
+
botAvatarUrl,
|
|
128
|
+
});
|
|
129
|
+
if (!result.ok || !result.imagePath) {
|
|
130
|
+
return `生成状态图片失败: ${result.error || "未知错误"}`;
|
|
87
131
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
132
|
+
await sendImageFromSkillContext({
|
|
133
|
+
ctx,
|
|
134
|
+
event,
|
|
135
|
+
imagePath: result.imagePath,
|
|
136
|
+
quoteReply: false,
|
|
137
|
+
});
|
|
138
|
+
return "已发送状态图片";
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return `生成状态图片失败: ${error}`;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
} as AITool,
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const helpSkills: AISkill[] = [helpSkill, statusSkill];
|
|
93
148
|
|
|
94
149
|
export default helpSkills;
|