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.
@@ -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 "./shared";
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.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
- const miokiVersion = await getPackageVersion(
42
- path.join(process.cwd(), "node_modules/mioku/node_modules/mioki/package.json"),
43
- );
44
- const miokuVersion = await getPackageVersion(
45
- path.join(process.cwd(), "node_modules/mioku/package.json"),
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.0.0",
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 "./shared";
9
+ } from "./help";
10
+ import { getRenderVersions } from "./utils";
11
+ import { generateStatusImage } from "./status";
11
12
 
12
- const helpSkills: AISkill[] = [
13
- {
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
- }
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
- 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 getHelpRenderVersions();
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
- if (!screenshotService) {
56
- return "screenshot 服务未加载,无法生成帮助图片";
57
- }
55
+ if (!screenshotService) {
56
+ return "screenshot 服务未加载,无法生成帮助图片";
57
+ }
58
58
 
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
- });
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
- if (!imagePath) {
74
- return "生成帮助图片失败";
75
- }
73
+ if (!imagePath) {
74
+ return "生成帮助图片失败";
75
+ }
76
76
 
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}`;
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
- } as AITool,
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;