koishi-plugin-dedide-ercorrection 0.0.0-stage → 0.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/lib/index.d.ts +9 -0
- package/lib/index.js +153 -0
- package/package.json +27 -4
- package/readme.md +5 -0
- package/README.md +0 -3
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Schema, Context } from 'koishi';
|
|
2
|
+
export declare const name = "dedide-ercorrection";
|
|
3
|
+
export declare const inject: any[];
|
|
4
|
+
export interface Config {
|
|
5
|
+
maxLength?: number;
|
|
6
|
+
verbose?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const Config: Schema<Config>;
|
|
9
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name2 in all)
|
|
7
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var src_exports = {};
|
|
21
|
+
__export(src_exports, {
|
|
22
|
+
Config: () => Config,
|
|
23
|
+
apply: () => apply,
|
|
24
|
+
inject: () => inject,
|
|
25
|
+
name: () => name
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
28
|
+
var import_koishi = require("koishi");
|
|
29
|
+
var import_de_corrector = require("@azulores/de-corrector");
|
|
30
|
+
var name = "dedide-ercorrection";
|
|
31
|
+
var inject = [];
|
|
32
|
+
var Config = import_koishi.Schema.object({
|
|
33
|
+
maxLength: import_koishi.Schema.number().description("\u8D85\u8FC7\u6B64\u957F\u5EA6\u7684\u6D88\u606F\u4E0D\u5904\u7406\uFF080=\u4E0D\u9650\u5236\uFF09").default(500).min(0),
|
|
34
|
+
verbose: import_koishi.Schema.boolean().description("\u8BB0\u5F55\u6BCF\u6B21\u4FEE\u6539").default(false)
|
|
35
|
+
});
|
|
36
|
+
var PLACEHOLDER_RE = /(\s|<at[^>]*\/?>|\[CQ:[^\]]+\])/g;
|
|
37
|
+
function extractPlaceholders(text) {
|
|
38
|
+
const items = [];
|
|
39
|
+
let clean = "";
|
|
40
|
+
let last = 0;
|
|
41
|
+
for (const m of text.matchAll(PLACEHOLDER_RE)) {
|
|
42
|
+
clean += text.slice(last, m.index);
|
|
43
|
+
items.push({ content: m[0], pos: Array.from(clean).length });
|
|
44
|
+
last = m.index + m[0].length;
|
|
45
|
+
}
|
|
46
|
+
clean += text.slice(last);
|
|
47
|
+
return { clean, items };
|
|
48
|
+
}
|
|
49
|
+
function restorePlaceholders(text, items) {
|
|
50
|
+
const chars = Array.from(text);
|
|
51
|
+
for (const { content, pos } of [...items].sort((a, b) => b.pos - a.pos)) {
|
|
52
|
+
chars.splice(Math.min(pos, chars.length), 0, content);
|
|
53
|
+
}
|
|
54
|
+
return chars.join("");
|
|
55
|
+
}
|
|
56
|
+
function contentToText(content) {
|
|
57
|
+
if (typeof content === "string")
|
|
58
|
+
return content;
|
|
59
|
+
if (Array.isArray(content)) {
|
|
60
|
+
return content.map((p) => typeof p === "string" ? p : p?.text ?? "").join("");
|
|
61
|
+
}
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
function apply(ctx, config) {
|
|
65
|
+
const logger = ctx.logger("dedide-ercorrection");
|
|
66
|
+
const maxLength = config.maxLength ?? 500;
|
|
67
|
+
const corrector = new import_de_corrector.DeCorrector({ maxLength });
|
|
68
|
+
let ready = false;
|
|
69
|
+
corrector.warmup().then(
|
|
70
|
+
() => {
|
|
71
|
+
ready = true;
|
|
72
|
+
logger.info("model ready (local ONNX, offline)");
|
|
73
|
+
},
|
|
74
|
+
(e) => logger.warn("model load failed: %s", e?.message ?? e)
|
|
75
|
+
);
|
|
76
|
+
const stats = {
|
|
77
|
+
messages: 0,
|
|
78
|
+
// 含 的地得 且通过长度检查的消息数
|
|
79
|
+
fixed: 0,
|
|
80
|
+
// 实际改动的消息数
|
|
81
|
+
skipped: 0,
|
|
82
|
+
// 检查后无需改动
|
|
83
|
+
errors: 0
|
|
84
|
+
// 处理异常
|
|
85
|
+
};
|
|
86
|
+
const dumpStats = () => {
|
|
87
|
+
logger.info(
|
|
88
|
+
"[stats] msgs=%d fixed=%d skipped=%d err=%d",
|
|
89
|
+
stats.messages,
|
|
90
|
+
stats.fixed,
|
|
91
|
+
stats.skipped,
|
|
92
|
+
stats.errors
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
ctx.setInterval(dumpStats, 6e4);
|
|
96
|
+
ctx.on("dispose", dumpStats);
|
|
97
|
+
async function correctMessage(content) {
|
|
98
|
+
if (!content || content.length <= 2)
|
|
99
|
+
return null;
|
|
100
|
+
if (!/[的地得]/.test(content))
|
|
101
|
+
return null;
|
|
102
|
+
if (maxLength > 0 && Array.from(content).length > maxLength)
|
|
103
|
+
return null;
|
|
104
|
+
if (!ready)
|
|
105
|
+
return null;
|
|
106
|
+
stats.messages++;
|
|
107
|
+
const { clean, items } = extractPlaceholders(content);
|
|
108
|
+
if (!clean)
|
|
109
|
+
return null;
|
|
110
|
+
let fixed;
|
|
111
|
+
try {
|
|
112
|
+
fixed = await corrector.fix(clean);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
stats.errors++;
|
|
115
|
+
logger.warn("correction failed: %s", e?.message ?? e);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (fixed === clean) {
|
|
119
|
+
stats.skipped++;
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const result = restorePlaceholders(fixed, items);
|
|
123
|
+
if (Array.from(result).length !== Array.from(content).length) {
|
|
124
|
+
stats.errors++;
|
|
125
|
+
logger.warn("[guard] length mismatch, dropped: %j -> %j", content, result);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (result === content) {
|
|
129
|
+
stats.skipped++;
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
stats.fixed++;
|
|
133
|
+
if (config.verbose)
|
|
134
|
+
logger.info("[fix] %j -> %j", content, result);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
const root = ctx.root ?? ctx;
|
|
138
|
+
ctx.dedideErcCorrect = correctMessage;
|
|
139
|
+
root["dedideErcCorrect"] = correctMessage;
|
|
140
|
+
ctx.dedideErcReady = () => ready;
|
|
141
|
+
root["dedideErcReady"] = () => ready;
|
|
142
|
+
ctx.middleware(async (session, next) => {
|
|
143
|
+
const result = await correctMessage(contentToText(session.content));
|
|
144
|
+
return result ?? next();
|
|
145
|
+
}, true);
|
|
146
|
+
}
|
|
147
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
148
|
+
0 && (module.exports = {
|
|
149
|
+
Config,
|
|
150
|
+
apply,
|
|
151
|
+
inject,
|
|
152
|
+
name
|
|
153
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-dedide-ercorrection",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"description": "中文「的/地/得」混用纠错 for Koishi:本地 ONNX 模型,无需联网、无需 LLM,@人/表情占位符保留",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"chatbot",
|
|
13
|
+
"koishi",
|
|
14
|
+
"plugin",
|
|
15
|
+
"chinese",
|
|
16
|
+
"nlp",
|
|
17
|
+
"的地得",
|
|
18
|
+
"correction"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.json && esbuild src/index.ts --bundle --platform=node --format=cjs --target=node20 --external:koishi --external:@azulores/de-corrector --outfile=lib/index.js"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@azulores/de-corrector": "^0.1.1"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"koishi": "^4.18.11"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/readme.md
ADDED
package/README.md
DELETED