koishi-plugin-media-luna 0.0.4 → 0.0.5
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/client/api.ts +36 -86
- package/client/components/ChannelsView.vue +28 -208
- package/client/components/GenerateView.vue +46 -11
- package/client/components/HistoryGallery.vue +47 -12
- package/client/components/PresetsView.vue +26 -200
- package/client/components/SettingsView.vue +26 -0
- package/client/components/TasksView.vue +15 -68
- package/client/composables/index.ts +14 -0
- package/client/composables/useDataFetch.ts +102 -0
- package/client/composables/useDialog.ts +58 -0
- package/client/composables/useLoading.ts +84 -0
- package/client/composables/usePagination.ts +110 -0
- package/client/constants/categories.ts +36 -0
- package/client/constants/index.ts +5 -0
- package/client/constants/phases.ts +44 -0
- package/client/styles/shared.css +42 -0
- package/client/types.ts +73 -0
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/lib/core/api/plugin-api.d.ts.map +1 -1
- package/lib/core/api/plugin-api.js +31 -0
- package/lib/core/api/plugin-api.js.map +1 -1
- package/lib/core/medialuna.service.d.ts.map +1 -1
- package/lib/core/medialuna.service.js +16 -12
- package/lib/core/medialuna.service.js.map +1 -1
- package/lib/core/pipeline/generation-pipeline.d.ts +4 -0
- package/lib/core/pipeline/generation-pipeline.d.ts.map +1 -1
- package/lib/core/pipeline/generation-pipeline.js +61 -20
- package/lib/core/pipeline/generation-pipeline.js.map +1 -1
- package/lib/core/plugin-loader.d.ts +42 -0
- package/lib/core/plugin-loader.d.ts.map +1 -1
- package/lib/core/plugin-loader.js +204 -1
- package/lib/core/plugin-loader.js.map +1 -1
- package/lib/core/request.service.d.ts +4 -1
- package/lib/core/request.service.d.ts.map +1 -1
- package/lib/core/request.service.js +11 -1
- package/lib/core/request.service.js.map +1 -1
- package/lib/core/types.d.ts +10 -0
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/README.md +716 -0
- package/lib/plugins/cache/middleware.d.ts.map +1 -1
- package/lib/plugins/cache/middleware.js +2 -0
- package/lib/plugins/cache/middleware.js.map +1 -1
- package/lib/plugins/task/middleware.d.ts.map +1 -1
- package/lib/plugins/task/middleware.js +20 -2
- package/lib/plugins/task/middleware.js.map +1 -1
- package/lib/types/index.d.ts +4 -0
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
# Media Luna 插件开发指南
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
Media Luna 采用插件化架构,所有功能(缓存、预设、计费、连接器等)都以插件形式实现。本文档介绍如何开发 Media Luna 插件。
|
|
6
|
+
|
|
7
|
+
## 快速开始
|
|
8
|
+
|
|
9
|
+
### 最小插件示例
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { definePlugin } from '../core'
|
|
13
|
+
|
|
14
|
+
export default definePlugin({
|
|
15
|
+
id: 'my-plugin',
|
|
16
|
+
name: '我的插件',
|
|
17
|
+
description: '插件描述',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
|
|
20
|
+
async onLoad(ctx) {
|
|
21
|
+
ctx.logger.info('插件已加载')
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 插件目录结构
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
src/plugins/my-plugin/
|
|
30
|
+
├── index.ts # 插件入口,导出 definePlugin()
|
|
31
|
+
├── config.ts # 配置类型和字段定义
|
|
32
|
+
├── service.ts # 服务类(可选)
|
|
33
|
+
├── middleware.ts # 中间件定义(可选)
|
|
34
|
+
└── README.md # 插件文档(可选)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 插件定义 (PluginDefinition)
|
|
40
|
+
|
|
41
|
+
### 完整接口
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
interface PluginDefinition {
|
|
45
|
+
// ===== 基础信息 =====
|
|
46
|
+
id: string // 唯一标识符(必须)
|
|
47
|
+
name: string // 显示名称(必须)
|
|
48
|
+
description?: string // 描述
|
|
49
|
+
version?: string // 版本号
|
|
50
|
+
dependencies?: string[] // 依赖的其他插件 ID
|
|
51
|
+
|
|
52
|
+
// ===== 配置 =====
|
|
53
|
+
configFields?: ConfigField[] // 配置字段定义(自动生成 UI)
|
|
54
|
+
configDefaults?: Record<string, any> // 默认配置值
|
|
55
|
+
|
|
56
|
+
// ===== 功能注册 =====
|
|
57
|
+
services?: ServiceDefinition[] // 服务
|
|
58
|
+
middlewares?: MiddlewareDefinition[] // 中间件
|
|
59
|
+
connector?: ConnectorDefinition // 连接器(每个插件最多一个)
|
|
60
|
+
|
|
61
|
+
// ===== UI =====
|
|
62
|
+
settingsActions?: SettingsAction[] // 设置面板操作按钮
|
|
63
|
+
|
|
64
|
+
// ===== 生命周期 =====
|
|
65
|
+
onLoad?: (ctx: PluginContext) => Promise<void> | void
|
|
66
|
+
onUnload?: () => Promise<void> | void
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 配置系统
|
|
73
|
+
|
|
74
|
+
### 配置字段类型
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
interface ConfigField {
|
|
78
|
+
key: string // 配置键名
|
|
79
|
+
label: string // 显示标签
|
|
80
|
+
type: 'text' | 'number' | 'boolean' | 'select' | 'password' | 'textarea'
|
|
81
|
+
default?: any // 默认值
|
|
82
|
+
description?: string // 字段说明
|
|
83
|
+
placeholder?: string // 占位符
|
|
84
|
+
options?: Array<{ label: string; value: any }> // select 类型的选项
|
|
85
|
+
showWhen?: { field: string; value: any } // 条件显示
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 示例
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
export const myConfigFields: ConfigField[] = [
|
|
93
|
+
{
|
|
94
|
+
key: 'enabled',
|
|
95
|
+
label: '启用插件',
|
|
96
|
+
type: 'boolean',
|
|
97
|
+
default: true
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: 'apiKey',
|
|
101
|
+
label: 'API 密钥',
|
|
102
|
+
type: 'password',
|
|
103
|
+
description: '从服务商获取的 API Key'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
key: 'mode',
|
|
107
|
+
label: '工作模式',
|
|
108
|
+
type: 'select',
|
|
109
|
+
default: 'auto',
|
|
110
|
+
options: [
|
|
111
|
+
{ label: '自动', value: 'auto' },
|
|
112
|
+
{ label: '手动', value: 'manual' }
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: 'endpoint',
|
|
117
|
+
label: '自定义端点',
|
|
118
|
+
type: 'text',
|
|
119
|
+
placeholder: 'https://api.example.com',
|
|
120
|
+
showWhen: { field: 'mode', value: 'manual' } // 仅在 mode='manual' 时显示
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 配置热重载
|
|
126
|
+
|
|
127
|
+
配置通过 Proxy 代理实现热重载。服务中访问配置时,始终获取最新值:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
class MyService {
|
|
131
|
+
private config: MyPluginConfig
|
|
132
|
+
|
|
133
|
+
constructor(ctx: Context, config: MyPluginConfig) {
|
|
134
|
+
this.config = config // 这是一个 Proxy
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
doSomething() {
|
|
138
|
+
// 每次访问都是最新配置,无需手动刷新
|
|
139
|
+
if (this.config.enabled) {
|
|
140
|
+
// ...
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 插件配置 vs 连接器配置
|
|
147
|
+
|
|
148
|
+
Media Luna 有两种配置,理解它们的区别很重要:
|
|
149
|
+
|
|
150
|
+
| 类型 | 作用域 | 存储位置 | 使用场景 |
|
|
151
|
+
|-----|-------|---------|---------|
|
|
152
|
+
| **插件配置** (configFields) | 全局 | `data/media-luna/config.yaml` | 插件级功能开关、全局参数 |
|
|
153
|
+
| **连接器配置** (connector.configFields) | 渠道级 | 数据库 (渠道表) | 每个渠道的 API 配置 |
|
|
154
|
+
|
|
155
|
+
**插件配置**:
|
|
156
|
+
- 在「设置 → 扩展插件」中配置
|
|
157
|
+
- 所有渠道共享同一份配置
|
|
158
|
+
- 适用于:缓存目录、同步间隔、功能开关等
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// 插件配置示例
|
|
162
|
+
configFields: [
|
|
163
|
+
{ key: 'enabled', label: '启用', type: 'boolean' },
|
|
164
|
+
{ key: 'cacheDir', label: '缓存目录', type: 'text' }
|
|
165
|
+
]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**连接器配置**:
|
|
169
|
+
- 在「渠道管理 → 编辑渠道」中配置
|
|
170
|
+
- 每个渠道可以有不同的配置
|
|
171
|
+
- 适用于:API Key、端点地址、模型名称等
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// 连接器配置示例
|
|
175
|
+
connector: {
|
|
176
|
+
id: 'my-api',
|
|
177
|
+
configFields: [
|
|
178
|
+
{ key: 'apiKey', label: 'API Key', type: 'password' },
|
|
179
|
+
{ key: 'model', label: '模型', type: 'select', options: [...] }
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 服务 (Service)
|
|
187
|
+
|
|
188
|
+
服务是单例对象,供其他中间件或插件使用。
|
|
189
|
+
|
|
190
|
+
### 定义服务
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
interface ServiceDefinition {
|
|
194
|
+
name: string // 服务名称(全局唯一)
|
|
195
|
+
factory: (ctx: PluginContext) => any // 工厂函数
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 示例
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// service.ts
|
|
203
|
+
export class MyService {
|
|
204
|
+
private ctx: Context
|
|
205
|
+
private config: MyPluginConfig
|
|
206
|
+
|
|
207
|
+
constructor(ctx: Context, config: MyPluginConfig) {
|
|
208
|
+
this.ctx = ctx
|
|
209
|
+
this.config = config
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async doWork(): Promise<string> {
|
|
213
|
+
return `Using API: ${this.config.apiKey}`
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// index.ts
|
|
218
|
+
export default definePlugin({
|
|
219
|
+
id: 'my-plugin',
|
|
220
|
+
services: [
|
|
221
|
+
{
|
|
222
|
+
name: 'myService',
|
|
223
|
+
factory: (ctx) => {
|
|
224
|
+
const config = ctx.getConfig<MyPluginConfig>()
|
|
225
|
+
return new MyService(ctx.ctx, config)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
})
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 使用服务
|
|
233
|
+
|
|
234
|
+
在中间件或其他插件中:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// 在中间件中
|
|
238
|
+
async execute(mctx: MiddlewareContext, next) {
|
|
239
|
+
const myService = mctx.getService<MyService>('myService')
|
|
240
|
+
if (myService) {
|
|
241
|
+
await myService.doWork()
|
|
242
|
+
}
|
|
243
|
+
return next()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 在 PluginContext 中
|
|
247
|
+
async onLoad(ctx) {
|
|
248
|
+
const otherService = ctx.getService<OtherService>('otherService')
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 中间件 (Middleware)
|
|
255
|
+
|
|
256
|
+
中间件参与生成请求的处理流程。
|
|
257
|
+
|
|
258
|
+
### 执行阶段
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
lifecycle-prepare → 准备阶段(校验、预处理)
|
|
262
|
+
lifecycle-pre-request → 请求前(预设应用、提示词处理)
|
|
263
|
+
lifecycle-request → 请求阶段(调用连接器生成)
|
|
264
|
+
lifecycle-post-request → 请求后(缓存、后处理)
|
|
265
|
+
lifecycle-finalize → 完成阶段(计费结算、任务记录)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 定义中间件
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
interface MiddlewareDefinition {
|
|
272
|
+
name: string // 中间件名称(必须唯一)
|
|
273
|
+
displayName: string // 显示名称
|
|
274
|
+
description?: string // 描述
|
|
275
|
+
phase: MiddlewarePhase // 执行阶段
|
|
276
|
+
category?: string // 所属插件 ID(用于配置关联)
|
|
277
|
+
priority?: number // 同阶段内的优先级(越小越先执行)
|
|
278
|
+
|
|
279
|
+
// 依赖声明
|
|
280
|
+
runBefore?: string[] // 在这些中间件之前运行
|
|
281
|
+
runAfter?: string[] // 在这些中间件之后运行
|
|
282
|
+
|
|
283
|
+
execute: (mctx: MiddlewareContext, next: () => Promise<MiddlewareRunStatus>)
|
|
284
|
+
=> Promise<MiddlewareRunStatus>
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 中间件上下文 (MiddlewareContext)
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
interface MiddlewareContext {
|
|
292
|
+
// Koishi 上下文
|
|
293
|
+
ctx: Context
|
|
294
|
+
session: Session | null
|
|
295
|
+
|
|
296
|
+
// 请求数据
|
|
297
|
+
prompt: string // 原始提示词
|
|
298
|
+
files: FileData[] // 输入文件
|
|
299
|
+
parameters: Record<string, any> // 请求参数
|
|
300
|
+
|
|
301
|
+
// 渠道信息
|
|
302
|
+
channelId: number
|
|
303
|
+
channel: ChannelConfig | null
|
|
304
|
+
|
|
305
|
+
// 输出(由中间件填充)
|
|
306
|
+
output: OutputAsset[] | null
|
|
307
|
+
|
|
308
|
+
// 用户标识
|
|
309
|
+
uid: number | null
|
|
310
|
+
|
|
311
|
+
// 跨中间件数据共享
|
|
312
|
+
store: Map<string, any>
|
|
313
|
+
|
|
314
|
+
// 方法
|
|
315
|
+
getMiddlewareConfig<T>(name: string): Promise<T | null>
|
|
316
|
+
setMiddlewareLog(name: string, data: any): void
|
|
317
|
+
getService<T>(name: string): T | undefined
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 返回状态
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
enum MiddlewareRunStatus {
|
|
325
|
+
CONTINUE = 'continue', // 继续执行后续中间件
|
|
326
|
+
STOP = 'stop', // 停止执行(正常终止)
|
|
327
|
+
SKIPPED = 'skipped' // 跳过(条件不满足)
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 示例
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { MiddlewareDefinition, MiddlewareRunStatus } from '../../core'
|
|
335
|
+
|
|
336
|
+
export function createMyMiddleware(): MiddlewareDefinition {
|
|
337
|
+
return {
|
|
338
|
+
name: 'my-middleware',
|
|
339
|
+
displayName: '我的中间件',
|
|
340
|
+
description: '处理某些逻辑',
|
|
341
|
+
phase: 'lifecycle-pre-request',
|
|
342
|
+
category: 'my-plugin', // 关联到 my-plugin 的配置
|
|
343
|
+
priority: 50,
|
|
344
|
+
|
|
345
|
+
async execute(mctx, next) {
|
|
346
|
+
// 获取配置
|
|
347
|
+
const config = await mctx.getMiddlewareConfig<MyPluginConfig>('my-plugin')
|
|
348
|
+
if (!config?.enabled) {
|
|
349
|
+
return next() // 未启用则跳过
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 处理逻辑
|
|
353
|
+
mctx.logger.info('Processing prompt: %s', mctx.prompt)
|
|
354
|
+
|
|
355
|
+
// 修改 prompt
|
|
356
|
+
// mctx.prompt = processedPrompt
|
|
357
|
+
|
|
358
|
+
// 使用 store 共享数据
|
|
359
|
+
mctx.store.set('myData', { processed: true })
|
|
360
|
+
|
|
361
|
+
// 记录日志(会显示在任务详情中)
|
|
362
|
+
mctx.setMiddlewareLog('my-middleware', {
|
|
363
|
+
processed: true,
|
|
364
|
+
originalLength: mctx.prompt.length
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
return next()
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 连接器 (Connector)
|
|
376
|
+
|
|
377
|
+
连接器提供实际的生成能力(调用 AI 服务)。
|
|
378
|
+
|
|
379
|
+
### 定义连接器
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
interface ConnectorDefinition {
|
|
383
|
+
id: string // 连接器 ID
|
|
384
|
+
name: string // 显示名称
|
|
385
|
+
description?: string // 描述
|
|
386
|
+
supportedTypes: MediaType[] // 支持的媒体类型: 'image' | 'audio' | 'video' | 'text'
|
|
387
|
+
|
|
388
|
+
configFields?: ConfigField[] // 渠道级配置字段
|
|
389
|
+
|
|
390
|
+
generate: (
|
|
391
|
+
request: ConnectorRequest,
|
|
392
|
+
config: Record<string, any>
|
|
393
|
+
) => Promise<ConnectorResponse>
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### 示例
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
export const myConnector: ConnectorDefinition = {
|
|
401
|
+
id: 'my-api',
|
|
402
|
+
name: 'My API',
|
|
403
|
+
description: '调用 My API 生成图片',
|
|
404
|
+
supportedTypes: ['image'],
|
|
405
|
+
|
|
406
|
+
configFields: [
|
|
407
|
+
{ key: 'apiUrl', label: 'API 地址', type: 'text', default: 'https://api.example.com' },
|
|
408
|
+
{ key: 'apiKey', label: 'API Key', type: 'password' },
|
|
409
|
+
{ key: 'model', label: '模型', type: 'text', default: 'default' }
|
|
410
|
+
],
|
|
411
|
+
|
|
412
|
+
async generate(request, config) {
|
|
413
|
+
const { prompt, parameters } = request
|
|
414
|
+
const { apiUrl, apiKey, model } = config
|
|
415
|
+
|
|
416
|
+
const response = await fetch(`${apiUrl}/generate`, {
|
|
417
|
+
method: 'POST',
|
|
418
|
+
headers: {
|
|
419
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
420
|
+
'Content-Type': 'application/json'
|
|
421
|
+
},
|
|
422
|
+
body: JSON.stringify({ prompt, model, ...parameters })
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
const data = await response.json()
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
success: true,
|
|
429
|
+
outputs: [
|
|
430
|
+
{ kind: 'image', url: data.imageUrl }
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// index.ts
|
|
437
|
+
export default definePlugin({
|
|
438
|
+
id: 'connector-my-api',
|
|
439
|
+
name: 'My API 连接器',
|
|
440
|
+
connector: myConnector
|
|
441
|
+
})
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## 生命周期钩子
|
|
447
|
+
|
|
448
|
+
### onLoad
|
|
449
|
+
|
|
450
|
+
插件加载时调用,可用于:
|
|
451
|
+
- 注册 Koishi 命令
|
|
452
|
+
- 注册 HTTP 路由
|
|
453
|
+
- 注册 Console API
|
|
454
|
+
- 初始化资源
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
async onLoad(ctx) {
|
|
458
|
+
// ctx.ctx 是 Koishi Context
|
|
459
|
+
// ctx.logger 是插件专用 logger
|
|
460
|
+
// ctx.getConfig() 获取配置
|
|
461
|
+
// ctx.getService() 获取其他服务
|
|
462
|
+
|
|
463
|
+
// 注册 HTTP 路由
|
|
464
|
+
ctx.ctx.inject(['server'], (injectedCtx) => {
|
|
465
|
+
injectedCtx.server.get('/my-plugin/status', (koaCtx) => {
|
|
466
|
+
koaCtx.body = { status: 'ok' }
|
|
467
|
+
})
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
// 注册 Console API
|
|
471
|
+
const console = ctx.ctx.console as any
|
|
472
|
+
console.addListener('media-luna/my-plugin/action', async (params) => {
|
|
473
|
+
// 处理前端请求
|
|
474
|
+
return { success: true, data: { ... } }
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
// 清理回调
|
|
478
|
+
ctx.onDispose(() => {
|
|
479
|
+
// 插件卸载时执行
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### onUnload
|
|
485
|
+
|
|
486
|
+
插件卸载时调用,用于清理资源。
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## 设置面板操作
|
|
491
|
+
|
|
492
|
+
在插件设置页面添加操作按钮:
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
settingsActions: [
|
|
496
|
+
{
|
|
497
|
+
name: 'sync',
|
|
498
|
+
label: '立即同步',
|
|
499
|
+
type: 'primary', // 'primary' | 'default' | 'error'
|
|
500
|
+
icon: 'sync', // Koishi 图标名
|
|
501
|
+
apiEvent: 'media-luna/my-plugin/sync' // 点击时触发的 Console 事件
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: 'clear',
|
|
505
|
+
label: '清空数据',
|
|
506
|
+
type: 'error',
|
|
507
|
+
icon: 'delete',
|
|
508
|
+
apiEvent: 'media-luna/my-plugin/clear'
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
需要在 `onLoad` 中注册对应的 API:
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
async onLoad(ctx) {
|
|
517
|
+
const console = ctx.ctx.console as any
|
|
518
|
+
|
|
519
|
+
console.addListener('media-luna/my-plugin/sync', async () => {
|
|
520
|
+
// 执行同步
|
|
521
|
+
return { success: true, data: { message: '同步完成' } }
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## 完整插件示例
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
// src/plugins/my-plugin/index.ts
|
|
532
|
+
|
|
533
|
+
import { definePlugin, MiddlewareRunStatus } from '../../core'
|
|
534
|
+
import type { MiddlewareContext } from '../../core'
|
|
535
|
+
|
|
536
|
+
interface MyPluginConfig {
|
|
537
|
+
enabled: boolean
|
|
538
|
+
prefix: string
|
|
539
|
+
maxLength: number
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const defaultConfig: MyPluginConfig = {
|
|
543
|
+
enabled: true,
|
|
544
|
+
prefix: '[Enhanced] ',
|
|
545
|
+
maxLength: 1000
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
class MyService {
|
|
549
|
+
constructor(private config: MyPluginConfig) {}
|
|
550
|
+
|
|
551
|
+
enhance(text: string): string {
|
|
552
|
+
if (!this.config.enabled) return text
|
|
553
|
+
const prefixed = this.config.prefix + text
|
|
554
|
+
return prefixed.slice(0, this.config.maxLength)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export default definePlugin({
|
|
559
|
+
id: 'my-plugin',
|
|
560
|
+
name: '我的增强插件',
|
|
561
|
+
description: '为提示词添加前缀',
|
|
562
|
+
version: '1.0.0',
|
|
563
|
+
|
|
564
|
+
configFields: [
|
|
565
|
+
{ key: 'enabled', label: '启用', type: 'boolean', default: true },
|
|
566
|
+
{ key: 'prefix', label: '前缀', type: 'text', default: '[Enhanced] ' },
|
|
567
|
+
{ key: 'maxLength', label: '最大长度', type: 'number', default: 1000 }
|
|
568
|
+
],
|
|
569
|
+
configDefaults: defaultConfig,
|
|
570
|
+
|
|
571
|
+
services: [
|
|
572
|
+
{
|
|
573
|
+
name: 'myEnhancer',
|
|
574
|
+
factory: (ctx) => new MyService(ctx.getConfig<MyPluginConfig>())
|
|
575
|
+
}
|
|
576
|
+
],
|
|
577
|
+
|
|
578
|
+
middlewares: [
|
|
579
|
+
{
|
|
580
|
+
name: 'my-enhancer',
|
|
581
|
+
displayName: '提示词增强',
|
|
582
|
+
phase: 'lifecycle-pre-request',
|
|
583
|
+
category: 'my-plugin',
|
|
584
|
+
priority: 10,
|
|
585
|
+
|
|
586
|
+
async execute(mctx: MiddlewareContext, next) {
|
|
587
|
+
const service = mctx.getService<MyService>('myEnhancer')
|
|
588
|
+
if (service) {
|
|
589
|
+
const original = mctx.prompt
|
|
590
|
+
mctx.prompt = service.enhance(mctx.prompt)
|
|
591
|
+
mctx.setMiddlewareLog('my-enhancer', {
|
|
592
|
+
original,
|
|
593
|
+
enhanced: mctx.prompt
|
|
594
|
+
})
|
|
595
|
+
}
|
|
596
|
+
return next()
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
],
|
|
600
|
+
|
|
601
|
+
async onLoad(ctx) {
|
|
602
|
+
ctx.logger.info('My plugin loaded with config: %o', ctx.getConfig())
|
|
603
|
+
}
|
|
604
|
+
})
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## 注意事项
|
|
610
|
+
|
|
611
|
+
### 1. 插件 ID 命名规范
|
|
612
|
+
|
|
613
|
+
- 使用 kebab-case:`my-plugin`, `connector-openai`
|
|
614
|
+
- 连接器插件建议以 `connector-` 开头
|
|
615
|
+
|
|
616
|
+
### 2. 配置键与插件 ID
|
|
617
|
+
|
|
618
|
+
中间件通过 `category` 字段关联到插件配置。确保:
|
|
619
|
+
- 中间件的 `category` 与插件 `id` 一致
|
|
620
|
+
- 或者使用 `getMiddlewareConfig(pluginId)` 时传入正确的插件 ID
|
|
621
|
+
|
|
622
|
+
### 3. 服务命名
|
|
623
|
+
|
|
624
|
+
服务名称全局唯一,建议使用插件 ID 作为前缀:
|
|
625
|
+
- `myPlugin` 或 `my-plugin-service`
|
|
626
|
+
|
|
627
|
+
### 4. 错误处理
|
|
628
|
+
|
|
629
|
+
中间件中抛出的错误会导致整个请求失败。对于非致命错误,建议:
|
|
630
|
+
- 记录日志
|
|
631
|
+
- 设置 middlewareLog
|
|
632
|
+
- 继续执行 `next()`
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
async execute(mctx, next) {
|
|
636
|
+
try {
|
|
637
|
+
// 可能失败的操作
|
|
638
|
+
} catch (e) {
|
|
639
|
+
mctx.setMiddlewareLog('my-middleware', { error: e.message })
|
|
640
|
+
// 非致命错误,继续执行
|
|
641
|
+
}
|
|
642
|
+
return next()
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
## 内置插件参考
|
|
649
|
+
|
|
650
|
+
查看以下内置插件作为开发参考:
|
|
651
|
+
|
|
652
|
+
| 插件 | 说明 | 关键特性 |
|
|
653
|
+
|-----|------|---------|
|
|
654
|
+
| `cache` | 缓存管理 | 服务 + 多中间件 + HTTP 路由 |
|
|
655
|
+
| `preset` | 预设系统 | 服务 + 远程同步 + 数据库 |
|
|
656
|
+
| `billing` | 计费系统 | 双中间件(预扣/结算) |
|
|
657
|
+
| `task` | 任务记录 | 数据库操作 + 统计 |
|
|
658
|
+
| `connector-*` | 各连接器 | 连接器定义示例 |
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## 外部插件(第三方插件)
|
|
663
|
+
|
|
664
|
+
Media Luna 支持从 npm 模块加载第三方插件。
|
|
665
|
+
|
|
666
|
+
### 外部插件格式
|
|
667
|
+
|
|
668
|
+
外部插件需要是一个 npm 包,默认导出 `PluginDefinition`:
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
// koishi-plugin-media-luna-xxx/src/index.ts
|
|
672
|
+
import { definePlugin } from 'koishi-plugin-media-luna'
|
|
673
|
+
|
|
674
|
+
export default definePlugin({
|
|
675
|
+
id: 'my-external-plugin',
|
|
676
|
+
name: '我的外部插件',
|
|
677
|
+
// ... 其他配置
|
|
678
|
+
})
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### 加载外部插件
|
|
682
|
+
|
|
683
|
+
通过 API 或配置加载:
|
|
684
|
+
|
|
685
|
+
```typescript
|
|
686
|
+
// 通过 API 加载
|
|
687
|
+
await ctx.mediaLuna.pluginLoader.addExternalPlugin('koishi-plugin-media-luna-xxx')
|
|
688
|
+
|
|
689
|
+
// 通过配置(config.yaml)
|
|
690
|
+
externalPlugins:
|
|
691
|
+
- koishi-plugin-media-luna-xxx
|
|
692
|
+
- ./path/to/local/plugin
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### 前端 API
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
import { pluginApi } from '@koishijs/plugin-media-luna/client'
|
|
699
|
+
|
|
700
|
+
// 获取已加载的外部插件
|
|
701
|
+
const externals = await pluginApi.externalList()
|
|
702
|
+
|
|
703
|
+
// 添加外部插件
|
|
704
|
+
await pluginApi.externalAdd('koishi-plugin-media-luna-xxx')
|
|
705
|
+
|
|
706
|
+
// 移除外部插件
|
|
707
|
+
await pluginApi.externalRemove('koishi-plugin-media-luna-xxx')
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
---
|
|
711
|
+
|
|
712
|
+
## TODO / 已知限制
|
|
713
|
+
|
|
714
|
+
1. **前端扩展组件** - 目前仅支持配置表单,自定义 Vue 组件尚未完善
|
|
715
|
+
2. **插件热重载** - 配置热重载已支持,但插件代码变更需要重启
|
|
716
|
+
3. **Koishi 命令封装** - 需要手动在 onLoad 中注册
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/plugins/cache/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,oBAAoB,EAIrB,MAAM,YAAY,CAAA;AA0EnB;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,oBAAoB,
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/plugins/cache/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,oBAAoB,EAIrB,MAAM,YAAY,CAAA;AA0EnB;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,oBAAoB,CA0EnE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,oBAAoB,CAyF9D"}
|