mioku-service-webui 2.1.0 → 4.0.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/config-page-loader.ts +20 -4
- package/database.ts +7 -7
- package/index.ts +3 -3
- package/package.json +15 -2
- package/routes/ai.ts +27 -1
- package/routes/data-management.ts +601 -0
- package/routes/db.ts +9 -6
- package/routes/index.ts +1 -0
- package/routes/store.ts +14 -9
- package/system.ts +430 -78
- package/types.ts +1 -1
- package/utils.ts +55 -23
package/config-page-loader.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { logger } from "mioki";
|
|
|
6
6
|
export interface ConfigField {
|
|
7
7
|
key: string;
|
|
8
8
|
label: string;
|
|
9
|
-
type: "text" | "textarea" | "number" | "switch" | "select" | "multi-select" | "secret" | "json";
|
|
9
|
+
type: "text" | "textarea" | "number" | "switch" | "select" | "multi-select" | "secret" | "json" | "array";
|
|
10
10
|
description?: string;
|
|
11
11
|
placeholder?: string;
|
|
12
12
|
required?: boolean;
|
|
@@ -14,6 +14,7 @@ export interface ConfigField {
|
|
|
14
14
|
source?: string;
|
|
15
15
|
options?: Array<{ value: string; label: string }>;
|
|
16
16
|
defaultValue?: any;
|
|
17
|
+
itemFields?: ConfigField[];
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export interface ConfigPageManifest {
|
|
@@ -26,10 +27,25 @@ export interface ConfigPageManifest {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export function loadPluginConfigPage(pluginName: string): ConfigPageManifest | null {
|
|
29
|
-
|
|
30
|
-
const
|
|
30
|
+
// 支持两种路径:本地 plugins 目录和 npm 包
|
|
31
|
+
const possiblePaths = [
|
|
32
|
+
path.join(process.cwd(), "plugins", pluginName),
|
|
33
|
+
path.join(process.cwd(), "node_modules", `mioku-plugin-${pluginName}`),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
let configMdPath: string | null = null;
|
|
37
|
+
let pluginDir: string | null = null;
|
|
38
|
+
|
|
39
|
+
for (const dir of possiblePaths) {
|
|
40
|
+
const candidate = path.join(dir, "config.md");
|
|
41
|
+
if (fs.existsSync(candidate)) {
|
|
42
|
+
configMdPath = candidate;
|
|
43
|
+
pluginDir = dir;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
31
47
|
|
|
32
|
-
if (!
|
|
48
|
+
if (!configMdPath || !pluginDir) {
|
|
33
49
|
return null;
|
|
34
50
|
}
|
|
35
51
|
|
package/database.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import Database from "
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
4
|
import { CHAT_DATA_DIR, readJsonFile, writeJsonFile } from "./utils";
|
|
5
5
|
|
|
6
6
|
const DB_PATH = path.join(CHAT_DATA_DIR, "chat.db");
|
|
@@ -10,7 +10,7 @@ function openDb(): any {
|
|
|
10
10
|
if (!fs.existsSync(DB_PATH)) {
|
|
11
11
|
throw new Error("数据库不存在");
|
|
12
12
|
}
|
|
13
|
-
return new Database(DB_PATH, {
|
|
13
|
+
return new Database(DB_PATH, { readwrite: true });
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function getTableColumns(db: any, table: string): string[] {
|
|
@@ -71,7 +71,7 @@ export function queryMessages(input: {
|
|
|
71
71
|
}
|
|
72
72
|
if (input.userId && columns.includes("user_id")) {
|
|
73
73
|
conditions.push("user_id = @userId");
|
|
74
|
-
params.userId = input.userId;
|
|
74
|
+
params.userId = String(input.userId);
|
|
75
75
|
}
|
|
76
76
|
if (input.sessionId && columns.includes("session_id")) {
|
|
77
77
|
conditions.push("session_id = @sessionId");
|
|
@@ -106,8 +106,8 @@ export function updateMessage(input: { table?: string; idField?: string; id: str
|
|
|
106
106
|
try {
|
|
107
107
|
const table = ensureSafeIdentifier(input.table || "messages", "表名");
|
|
108
108
|
const idField = ensureSafeIdentifier(input.idField || "id", "主键字段");
|
|
109
|
-
const stmt = db.prepare(`UPDATE ${table} SET content =
|
|
110
|
-
const result = stmt.run(input.content, input.id);
|
|
109
|
+
const stmt = db.prepare(`UPDATE ${table} SET content = $content WHERE ${idField} = $id`);
|
|
110
|
+
const result = stmt.run({ $content: input.content, $id: input.id });
|
|
111
111
|
return { changed: result.changes };
|
|
112
112
|
} finally {
|
|
113
113
|
db.close();
|
|
@@ -123,8 +123,8 @@ export function deleteMessagesByRange(input: { table?: string; startTime: number
|
|
|
123
123
|
if (!timeColumn) {
|
|
124
124
|
throw new Error(`${table} 不包含时间字段(created_at/timestamp/updated_at)`);
|
|
125
125
|
}
|
|
126
|
-
const stmt = db.prepare(`DELETE FROM ${table} WHERE ${timeColumn} >=
|
|
127
|
-
const result = stmt.run(input.startTime, input.endTime);
|
|
126
|
+
const stmt = db.prepare(`DELETE FROM ${table} WHERE ${timeColumn} >= $startTime AND ${timeColumn} <= $endTime`);
|
|
127
|
+
const result = stmt.run({ $startTime: input.startTime, $endTime: input.endTime });
|
|
128
128
|
return { deleted: result.changes };
|
|
129
129
|
} finally {
|
|
130
130
|
db.close();
|
package/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Hono } from "hono";
|
|
|
5
5
|
import { serve } from "@hono/node-server";
|
|
6
6
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
7
7
|
import { createNodeWebSocket } from "@hono/node-ws";
|
|
8
|
-
import type { MiokuService } from "
|
|
8
|
+
import type { MiokuService } from "mioku";
|
|
9
9
|
import type { WebUISettings } from "./types";
|
|
10
10
|
import { ensureAuthConfig, loginWithToken, requireAuth } from "./auth";
|
|
11
11
|
import { getWebUISettings, getSystemOverview, getSaying } from "./system";
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
createMemeRoutes,
|
|
21
21
|
createManageRoutes,
|
|
22
22
|
createStoreRoutes,
|
|
23
|
+
createDataManagementRoutes,
|
|
23
24
|
} from "./routes";
|
|
24
25
|
|
|
25
26
|
export interface WebUIServiceAPI {
|
|
@@ -98,6 +99,7 @@ class WebUIRuntime {
|
|
|
98
99
|
this.app.route("/api/db", createDBRoutes());
|
|
99
100
|
this.app.route("/api/plugin-config", createPluginConfigRoutes());
|
|
100
101
|
this.app.route("/api/meme", createMemeRoutes());
|
|
102
|
+
this.app.route("/api/data-management", createDataManagementRoutes());
|
|
101
103
|
|
|
102
104
|
this.app.get(
|
|
103
105
|
"/api/ws/logs",
|
|
@@ -218,8 +220,6 @@ const webUIService: MiokuService = {
|
|
|
218
220
|
async init() {
|
|
219
221
|
initBuiltinDatasources();
|
|
220
222
|
ensureAuthConfig();
|
|
221
|
-
fs.mkdirSync(CHAT_CONFIG_DIR, { recursive: true });
|
|
222
|
-
fs.mkdirSync(CHAT_DATA_DIR, { recursive: true });
|
|
223
223
|
runtime.initRoutes();
|
|
224
224
|
},
|
|
225
225
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-service-webui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Mioku WebUI 服务",
|
|
5
5
|
"main": "index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"webui:dev": "cd ../mioku-webui && bun run dev",
|
|
9
|
+
"webui:build": "cd ../mioku-webui && bun run build"
|
|
10
|
+
},
|
|
6
11
|
"keywords": [
|
|
7
12
|
"mioku"
|
|
8
13
|
],
|
|
@@ -13,8 +18,16 @@
|
|
|
13
18
|
"dependencies": {
|
|
14
19
|
"@hono/node-server": "1.19.13",
|
|
15
20
|
"@hono/node-ws": "^1.2.0",
|
|
16
|
-
"
|
|
21
|
+
"fflate": "^0.8.2",
|
|
17
22
|
"gray-matter": "^4.0.3",
|
|
18
23
|
"hono": "4.12.14"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"mioku-service-ai": "workspace:*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/bun": "^1.3.14",
|
|
30
|
+
"mioku": "workspace:*",
|
|
31
|
+
"mioku-service-ai": "workspace:*"
|
|
19
32
|
}
|
|
20
33
|
}
|
package/routes/ai.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { logger } from "mioki";
|
|
3
3
|
import { getChatConfig, updateChatConfig } from "../system";
|
|
4
|
-
import aiService from "
|
|
4
|
+
import aiService from "mioku-service-ai";
|
|
5
|
+
import type { AIUsageRange } from "mioku-service-ai/usage/types";
|
|
5
6
|
|
|
6
7
|
const DEFAULT_MEMORY_GROUP_HISTORY_LIMIT = 300;
|
|
7
8
|
const DEFAULT_MEMORY_USER_HISTORY_LIMIT = 100;
|
|
@@ -9,6 +10,7 @@ const DEFAULT_TOPIC_WINDOW_HOURS = 5;
|
|
|
9
10
|
const DEFAULT_TOPIC_HISTORY_WINDOW_COUNT = 3;
|
|
10
11
|
const DEFAULT_EXPRESSION_LEARN_AFTER_MESSAGES = 100;
|
|
11
12
|
const DEFAULT_EXPRESSION_SAMPLE_SIZE = 8;
|
|
13
|
+
const usageRanges = new Set<AIUsageRange>(["today", "7d", "30d"]);
|
|
12
14
|
|
|
13
15
|
function normalizePersonalizationConfig(input: any): any {
|
|
14
16
|
const data =
|
|
@@ -187,5 +189,29 @@ export function createAIRoutes() {
|
|
|
187
189
|
});
|
|
188
190
|
});
|
|
189
191
|
|
|
192
|
+
app.get("/usage", (c) => {
|
|
193
|
+
if (!aiService?.api?.getUsageSummary) {
|
|
194
|
+
return c.json({ ok: false, error: "AI_USAGE_UNAVAILABLE" }, 503);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const rawRange = c.req.query("range");
|
|
198
|
+
const range: AIUsageRange =
|
|
199
|
+
rawRange && usageRanges.has(rawRange as AIUsageRange)
|
|
200
|
+
? (rawRange as AIUsageRange)
|
|
201
|
+
: "today";
|
|
202
|
+
const rawBotId = c.req.query("botId");
|
|
203
|
+
const botId =
|
|
204
|
+
rawBotId && rawBotId !== "all" ? Number(rawBotId) : undefined;
|
|
205
|
+
|
|
206
|
+
if (botId !== undefined && !Number.isFinite(botId)) {
|
|
207
|
+
return c.json({ ok: false, error: "INVALID_BOT_ID" }, 400);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return c.json({
|
|
211
|
+
ok: true,
|
|
212
|
+
data: aiService.api.getUsageSummary({ range, botId }),
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
190
216
|
return app;
|
|
191
217
|
}
|