koishi-plugin-openai-compatible 1.0.5 → 1.0.6
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/README.md +153 -52
- package/lib/api.d.ts +7 -0
- package/lib/api.js +32 -0
- package/lib/config.d.ts +23 -0
- package/lib/config.js +54 -0
- package/lib/emotion.d.ts +9 -0
- package/lib/emotion.js +32 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +157 -0
- package/lib/locales/zh.d.ts +44 -0
- package/lib/locales/zh.js +45 -0
- package/lib/storage.d.ts +10 -0
- package/lib/storage.js +25 -0
- package/lib/types.d.ts +32 -0
- package/lib/types.js +2 -0
- package/package.json +1 -1
- package/src/api.ts +40 -0
- package/src/config.ts +91 -0
- package/src/emotion.ts +37 -0
- package/src/index.ts +157 -99
- package/src/locales/zh.ts +43 -0
- package/src/storage.ts +27 -0
- package/src/types.ts +34 -0
- package/tsconfig.json +12 -5
- package/dist/index.js +0 -97
package/README.md
CHANGED
|
@@ -1,60 +1,161 @@
|
|
|
1
1
|
# koishi-plugin-openai-compatible
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个支持所有兼容 OpenAI API 的 Koishi 插件,提供完整的 AI 对话功能,包括黑名单、请求冷却、自定义指令和情绪分析等高级功能。
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- ✅ 支持所有兼容 OpenAI 的 API(OpenAI、Azure OpenAI、各类国产大模型等)
|
|
8
|
+
- ✅ 可配置 API 端点、模型和参数
|
|
9
|
+
- ✅ 可设置自定义系统提示词
|
|
10
|
+
- ✅ 支持黑名单功能,阻止特定用户使用
|
|
11
|
+
- ✅ 支持请求冷却配置,防止滥用
|
|
12
|
+
- ✅ 支持自定义指令名
|
|
13
|
+
- ✅ 支持控制是否开启提示(关闭后所有错误提示都不会输出)
|
|
14
|
+
- ✅ 支持情绪分析功能(可选)
|
|
15
|
+
- 分析 AI 输出内容的情绪
|
|
16
|
+
- 自动添加对应的情绪表情包
|
|
17
|
+
- 支持自定义情绪与表情包的映射
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
11
20
|
|
|
12
|
-
## Installation
|
|
13
21
|
```bash
|
|
22
|
+
# 使用 npm
|
|
14
23
|
npm install koishi-plugin-openai-compatible
|
|
24
|
+
|
|
25
|
+
# 或使用 yarn
|
|
26
|
+
yarn add koishi-plugin-openai-compatible
|
|
27
|
+
|
|
28
|
+
# 或使用 pnpm
|
|
29
|
+
pnpm add koishi-plugin-openai-compatible
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 配置
|
|
33
|
+
|
|
34
|
+
在 Koishi 配置文件中启用插件并配置:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
plugins:
|
|
38
|
+
openai-compatible:
|
|
39
|
+
# API 配置
|
|
40
|
+
endpoint: 'https://api.openai.com/v1/chat/completions'
|
|
41
|
+
apiKey: 'your-api-key-here'
|
|
42
|
+
model: 'gpt-3.5-turbo'
|
|
43
|
+
|
|
44
|
+
# 情绪分析配置
|
|
45
|
+
enableEmotion: false # 是否启用情绪分析
|
|
46
|
+
emotionEndpoint: 'https://api.openai.com/v1/chat/completions'
|
|
47
|
+
emotionApiKey: 'your-emotion-api-key'
|
|
48
|
+
emotionModel: 'gpt-3.5-turbo' # 推荐使用较快的模型
|
|
49
|
+
emotionImages:
|
|
50
|
+
'快乐': 'https://example.com/happy.gif'
|
|
51
|
+
'伤心': 'https://example.com/sad.gif'
|
|
52
|
+
'生气': 'https://example.com/angry.gif'
|
|
53
|
+
|
|
54
|
+
# 提示词配置
|
|
55
|
+
systemPrompt: '你是一个友好的 AI 助手。'
|
|
56
|
+
emotionSystemPrompt: '分析文本的情绪,只返回情绪名称。'
|
|
57
|
+
|
|
58
|
+
# 参数配置
|
|
59
|
+
temperature: 0.7
|
|
60
|
+
maxTokens: 2000
|
|
61
|
+
topP: 1
|
|
62
|
+
frequencyPenalty: 0
|
|
63
|
+
presencePenalty: 0
|
|
64
|
+
|
|
65
|
+
# 黑名单和冷却
|
|
66
|
+
blacklist: []
|
|
67
|
+
cooldown: 10000 # 10 秒
|
|
68
|
+
|
|
69
|
+
# 命令名和提示
|
|
70
|
+
commandName: 'ai'
|
|
71
|
+
enableMessage: true # 是否开启提示(关闭后所有错误提示都不会输出)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 情绪分析配置
|
|
75
|
+
|
|
76
|
+
启用情绪分析功能时,需要注意:
|
|
77
|
+
|
|
78
|
+
1. **推荐使用较快的模型**:情绪分析需要额外一次 API 调用,建议使用响应速度快的模型(如 `gpt-3.5-turbo`)
|
|
79
|
+
2. **配置情绪映射**:在 `emotionImages` 中配置情绪名称与表情包图片链接的映射
|
|
80
|
+
3. **系统提示词**:插件内置了严格的系统提示词,确保情绪分析模型只返回情绪名称
|
|
81
|
+
|
|
82
|
+
### 情绪类型
|
|
83
|
+
|
|
84
|
+
支持的情绪类型:
|
|
85
|
+
- 快乐、开心、高兴
|
|
86
|
+
- 伤心、难过、悲伤
|
|
87
|
+
- 生气、愤怒
|
|
88
|
+
- 惊讶
|
|
89
|
+
- 疑惑
|
|
90
|
+
- 害怕、恐惧
|
|
91
|
+
- 平静、中立
|
|
92
|
+
|
|
93
|
+
## 使用方法
|
|
94
|
+
|
|
95
|
+
### 基本对话
|
|
96
|
+
|
|
15
97
|
```
|
|
98
|
+
.ai 你好
|
|
99
|
+
.ai 解释什么是量子力学
|
|
100
|
+
.ai 写一首关于春天的诗
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 管理命令
|
|
104
|
+
|
|
105
|
+
需要管理员权限:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
# 黑名单管理
|
|
109
|
+
.ai.admin.add <userId> # 添加用户到黑名单
|
|
110
|
+
.ai.admin.remove <userId> # 从黑名单移除用户
|
|
111
|
+
.ai.admin.list # 查看黑名单
|
|
112
|
+
|
|
113
|
+
# 冷却管理
|
|
114
|
+
.ai.admin.clearcooldown <userId> # 清除用户冷却时间
|
|
115
|
+
|
|
116
|
+
# 状态查看
|
|
117
|
+
.ai.admin.status # 查看插件状态
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## API 兼容性
|
|
121
|
+
|
|
122
|
+
本插件支持所有兼容 OpenAI API 格式的服务:
|
|
123
|
+
|
|
124
|
+
- OpenAI 官方 API
|
|
125
|
+
- Azure OpenAI
|
|
126
|
+
- 通义千问(通过兼容 API)
|
|
127
|
+
- 智谱 AI(通过兼容 API)
|
|
128
|
+
- 百度文心一言(通过兼容 API)
|
|
129
|
+
- 其他兼容 OpenAI API 格式的服务
|
|
130
|
+
|
|
131
|
+
### 使用自定义 API 示例
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
plugins:
|
|
135
|
+
openai-compatible:
|
|
136
|
+
endpoint: 'https://your-custom-api.com/v1/chat/completions'
|
|
137
|
+
apiKey: 'your-api-key'
|
|
138
|
+
model: 'your-model-name'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 注意事项
|
|
142
|
+
|
|
143
|
+
1. **API Key 安全**:请妥善保管您的 API Key,不要泄露给他人
|
|
144
|
+
2. **成本控制**:建议设置合理的冷却时间和令牌限制,避免产生高额费用
|
|
145
|
+
3. **情绪分析成本**:启用情绪分析会使每次请求的成本增加约一倍
|
|
146
|
+
4. **黑名单管理**:定期检查黑名单,及时移除已解除限制的用户
|
|
147
|
+
5. **提示控制**:关闭提示功能后,用户将不会收到任何错误提示(包括黑名单、冷却、输入为空、请求失败等),适用于需要静默处理的场景
|
|
148
|
+
|
|
149
|
+
## 开发
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# 安装依赖
|
|
153
|
+
npm install
|
|
154
|
+
|
|
155
|
+
# 构建项目
|
|
156
|
+
npm run build
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## 许可证
|
|
16
160
|
|
|
17
|
-
|
|
18
|
-
```ts
|
|
19
|
-
export interface Config {
|
|
20
|
-
apiEndpoint: string
|
|
21
|
-
apiKey: string
|
|
22
|
-
model: string
|
|
23
|
-
temperature?: number
|
|
24
|
-
maxTokens?: number
|
|
25
|
-
systemPrompt?: string
|
|
26
|
-
commandName?: string
|
|
27
|
-
triggerPrefix?: string
|
|
28
|
-
userPrompts?: Record<string, string>
|
|
29
|
-
thinkingPrompt?: string
|
|
30
|
-
blacklist?: string[]
|
|
31
|
-
blacklistResponse?: string
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Usage
|
|
36
|
-
1. **Command Mode**: If `commandName` is set, use `/commandName your question`.
|
|
37
|
-
2. **Message Mode**: If no `commandName` is set, all messages trigger the plugin (filtered by `triggerPrefix` if set).
|
|
38
|
-
|
|
39
|
-
## Example
|
|
40
|
-
```ts
|
|
41
|
-
// koishi.config.ts
|
|
42
|
-
import OpenAICompatible from 'koishi-plugin-openai-compatible'
|
|
43
|
-
|
|
44
|
-
export default {
|
|
45
|
-
plugins: {
|
|
46
|
-
'openai-compatible': {
|
|
47
|
-
apiEndpoint: 'https://api.openai.com/v1',
|
|
48
|
-
apiKey: 'your-api-key',
|
|
49
|
-
model: 'gpt-3.5-turbo',
|
|
50
|
-
temperature: 1,
|
|
51
|
-
maxTokens: 2048,
|
|
52
|
-
systemPrompt: 'You are a helpful assistant.',
|
|
53
|
-
commandName: 'chat',
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## License
|
|
60
|
-
MIT
|
|
161
|
+
MIT License
|
package/lib/api.d.ts
ADDED
package/lib/api.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAICompatibleAPI = void 0;
|
|
4
|
+
class OpenAICompatibleAPI {
|
|
5
|
+
constructor(endpoint, apiKey) {
|
|
6
|
+
this.endpoint = endpoint;
|
|
7
|
+
this.apiKey = apiKey;
|
|
8
|
+
}
|
|
9
|
+
async chatCompletion(request) {
|
|
10
|
+
const headers = {
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
};
|
|
13
|
+
if (this.apiKey) {
|
|
14
|
+
headers['Authorization'] = `Bearer ${this.apiKey}`;
|
|
15
|
+
}
|
|
16
|
+
const response = await fetch(this.endpoint, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers,
|
|
19
|
+
body: JSON.stringify(request),
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
const errorText = await response.text();
|
|
23
|
+
throw new Error(`API 请求失败: ${response.status} ${response.statusText}\n${errorText}`);
|
|
24
|
+
}
|
|
25
|
+
const data = await response.json();
|
|
26
|
+
if (!data.choices || data.choices.length === 0) {
|
|
27
|
+
throw new Error('API 返回了空的选择列表');
|
|
28
|
+
}
|
|
29
|
+
return data.choices[0].message.content;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.OpenAICompatibleAPI = OpenAICompatibleAPI;
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Schema } from 'koishi';
|
|
2
|
+
export interface Config {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
model: string;
|
|
6
|
+
enableEmotion: boolean;
|
|
7
|
+
emotionEndpoint: string;
|
|
8
|
+
emotionApiKey: string;
|
|
9
|
+
emotionModel: string;
|
|
10
|
+
emotionImages: Record<string, string>;
|
|
11
|
+
systemPrompt: string;
|
|
12
|
+
emotionSystemPrompt: string;
|
|
13
|
+
temperature: number;
|
|
14
|
+
maxTokens: number;
|
|
15
|
+
topP: number;
|
|
16
|
+
frequencyPenalty: number;
|
|
17
|
+
presencePenalty: number;
|
|
18
|
+
blacklist: string[];
|
|
19
|
+
cooldown: number;
|
|
20
|
+
commandName: string;
|
|
21
|
+
enableMessage: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare const Config: Schema<Config>;
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Config = void 0;
|
|
4
|
+
const koishi_1 = require("koishi");
|
|
5
|
+
exports.Config = koishi_1.Schema.intersect([
|
|
6
|
+
koishi_1.Schema.object({
|
|
7
|
+
endpoint: koishi_1.Schema.string().default('https://api.openai.com/v1/chat/completions'),
|
|
8
|
+
apiKey: koishi_1.Schema.string().default('').description('OpenAI API Key'),
|
|
9
|
+
model: koishi_1.Schema.string().default('gpt-3.5-turbo'),
|
|
10
|
+
}).description('API 配置'),
|
|
11
|
+
koishi_1.Schema.object({
|
|
12
|
+
enableEmotion: koishi_1.Schema.boolean().default(false).description('是否启用情绪分析'),
|
|
13
|
+
emotionEndpoint: koishi_1.Schema.string().default('https://api.openai.com/v1/chat/completions'),
|
|
14
|
+
emotionApiKey: koishi_1.Schema.string().default('').description('情绪分析 API Key'),
|
|
15
|
+
emotionModel: koishi_1.Schema.string().default('gpt-3.5-turbo').description('推荐使用较快的模型'),
|
|
16
|
+
emotionImages: koishi_1.Schema.dict(koishi_1.Schema.string().description('情绪表情包图片链接')).default({
|
|
17
|
+
'快乐': 'https://example.com/happy.gif',
|
|
18
|
+
'开心': 'https://example.com/happy.gif',
|
|
19
|
+
'高兴': 'https://example.com/happy.gif',
|
|
20
|
+
'伤心': 'https://example.com/sad.gif',
|
|
21
|
+
'难过': 'https://example.com/sad.gif',
|
|
22
|
+
'悲伤': 'https://example.com/sad.gif',
|
|
23
|
+
'生气': 'https://example.com/angry.gif',
|
|
24
|
+
'愤怒': 'https://example.com/angry.gif',
|
|
25
|
+
'惊讶': 'https://example.com/surprised.gif',
|
|
26
|
+
'疑惑': 'https://example.com/confused.gif',
|
|
27
|
+
'害怕': 'https://example.com/afraid.gif',
|
|
28
|
+
'恐惧': 'https://example.com/afraid.gif',
|
|
29
|
+
'平静': 'https://example.com/calm.gif',
|
|
30
|
+
'中立': 'https://example.com/neutral.gif',
|
|
31
|
+
}).description('情绪名称对应的表情包图片链接'),
|
|
32
|
+
}).description('情绪分析配置'),
|
|
33
|
+
koishi_1.Schema.object({
|
|
34
|
+
systemPrompt: koishi_1.Schema.string()
|
|
35
|
+
.default('你是一个友好的 AI 助手,请用简洁、准确的语言回答用户的问题。')
|
|
36
|
+
.description('系统提示词'),
|
|
37
|
+
emotionSystemPrompt: koishi_1.Schema.string()
|
|
38
|
+
.default('你是一个情绪分析助手。分析给定的文本内容,并判断其中表达的情绪。\n\n情绪类型包括:快乐、开心、高兴、伤心、难过、悲伤、生气、愤怒、惊讶、疑惑、害怕、恐惧、平静、中立。\n\n请直接输出一个情绪名称,不要输出任何其他内容,不要解释,不要添加标点符号。\n\n例如:\n- "今天天气真好,我很开心!" -> 快乐\n- "这次考试失败了,我好难过。" -> 伤心\n- "什么?这太不可思议了!" -> 惊讶\n- "我不知道该怎么办。" -> 疑惑\n- "这真是太可怕了!" -> 害怕\n- "一切都很平静。" -> 平静')
|
|
39
|
+
.description('情绪分析系统提示词'),
|
|
40
|
+
}).description('提示词配置'),
|
|
41
|
+
koishi_1.Schema.object({
|
|
42
|
+
temperature: koishi_1.Schema.number().min(0).max(2).default(0.7).description('温度参数'),
|
|
43
|
+
maxTokens: koishi_1.Schema.number().min(1).default(2000).description('最大令牌数'),
|
|
44
|
+
topP: koishi_1.Schema.number().min(0).max(1).default(1).description('Top P 参数'),
|
|
45
|
+
frequencyPenalty: koishi_1.Schema.number().min(-2).max(2).default(0).description('频率惩罚'),
|
|
46
|
+
presencePenalty: koishi_1.Schema.number().min(-2).max(2).default(0).description('存在惩罚'),
|
|
47
|
+
}).description('参数配置'),
|
|
48
|
+
koishi_1.Schema.object({
|
|
49
|
+
blacklist: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description('黑名单用户 ID'),
|
|
50
|
+
cooldown: koishi_1.Schema.number().min(0).default(10000).description('请求冷却时间(毫秒)'),
|
|
51
|
+
commandName: koishi_1.Schema.string().default('ai').description('自定义指令名称'),
|
|
52
|
+
enableMessage: koishi_1.Schema.boolean().default(true).description('是否开启提示(关闭后所有错误提示都不会输出)'),
|
|
53
|
+
}).description('其他配置'),
|
|
54
|
+
]);
|
package/lib/emotion.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OpenAICompatibleAPI } from './api';
|
|
2
|
+
import type { Config } from './config';
|
|
3
|
+
export declare class EmotionAnalyzer {
|
|
4
|
+
private api;
|
|
5
|
+
private config;
|
|
6
|
+
constructor(api: OpenAICompatibleAPI, config: Config);
|
|
7
|
+
analyzeEmotion(text: string): Promise<string>;
|
|
8
|
+
getEmotionImage(emotion: string): string | null;
|
|
9
|
+
}
|
package/lib/emotion.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EmotionAnalyzer = void 0;
|
|
4
|
+
class EmotionAnalyzer {
|
|
5
|
+
constructor(api, config) {
|
|
6
|
+
this.api = api;
|
|
7
|
+
this.config = config;
|
|
8
|
+
}
|
|
9
|
+
async analyzeEmotion(text) {
|
|
10
|
+
const request = {
|
|
11
|
+
model: this.config.emotionModel,
|
|
12
|
+
messages: [
|
|
13
|
+
{ role: 'system', content: this.config.emotionSystemPrompt },
|
|
14
|
+
{ role: 'user', content: text },
|
|
15
|
+
],
|
|
16
|
+
temperature: 0.3,
|
|
17
|
+
max_tokens: 50,
|
|
18
|
+
};
|
|
19
|
+
try {
|
|
20
|
+
const emotion = await this.api.chatCompletion(request);
|
|
21
|
+
return emotion.trim();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error('情绪分析失败:', error);
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
getEmotionImage(emotion) {
|
|
29
|
+
return this.config.emotionImages[emotion] || null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.EmotionAnalyzer = EmotionAnalyzer;
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import type { Config } from './config';
|
|
3
|
+
export declare const name = "openai-compatible";
|
|
4
|
+
export declare const inject: {
|
|
5
|
+
required: never[];
|
|
6
|
+
optional: never[];
|
|
7
|
+
};
|
|
8
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inject = exports.name = void 0;
|
|
4
|
+
exports.apply = apply;
|
|
5
|
+
const api_1 = require("./api");
|
|
6
|
+
const emotion_1 = require("./emotion");
|
|
7
|
+
const storage_1 = require("./storage");
|
|
8
|
+
exports.name = 'openai-compatible';
|
|
9
|
+
exports.inject = {
|
|
10
|
+
required: [],
|
|
11
|
+
optional: [],
|
|
12
|
+
};
|
|
13
|
+
function apply(ctx, config) {
|
|
14
|
+
// 导出配置 Schema
|
|
15
|
+
ctx.i18n.define('zh', require('./locales/zh'));
|
|
16
|
+
// 初始化存储
|
|
17
|
+
const storage = new storage_1.Storage();
|
|
18
|
+
// 初始化 API 客户端
|
|
19
|
+
const api = new api_1.OpenAICompatibleAPI(config.endpoint, config.apiKey);
|
|
20
|
+
// 初始化情绪分析器
|
|
21
|
+
let emotionAnalyzer = null;
|
|
22
|
+
if (config.enableEmotion) {
|
|
23
|
+
const emotionApi = new api_1.OpenAICompatibleAPI(config.emotionEndpoint, config.emotionApiKey);
|
|
24
|
+
emotionAnalyzer = new emotion_1.EmotionAnalyzer(emotionApi, config);
|
|
25
|
+
}
|
|
26
|
+
// 注册命令
|
|
27
|
+
ctx.command(config.commandName, { authority: 1 })
|
|
28
|
+
.alias('chat')
|
|
29
|
+
.action(async ({ session }, content) => {
|
|
30
|
+
if (!session)
|
|
31
|
+
return;
|
|
32
|
+
const userId = session.userId;
|
|
33
|
+
if (!userId)
|
|
34
|
+
return;
|
|
35
|
+
// 检查黑名单
|
|
36
|
+
if (config.blacklist.includes(userId)) {
|
|
37
|
+
if (config.enableMessage) {
|
|
38
|
+
await session.send('您在黑名单中,无法使用此功能。');
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// 检查冷却时间
|
|
43
|
+
if (storage.isOnCooldown(userId, config.cooldown)) {
|
|
44
|
+
const data = storage.getCooldown(userId);
|
|
45
|
+
const remaining = config.cooldown - (Date.now() - data.lastRequestTime);
|
|
46
|
+
const remainingSeconds = Math.ceil(remaining / 1000);
|
|
47
|
+
if (config.enableMessage) {
|
|
48
|
+
await session.send(`请求过于频繁,请等待 ${remainingSeconds} 秒后再试。`);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// 获取用户输入
|
|
53
|
+
if (!content || content.trim() === '') {
|
|
54
|
+
if (config.enableMessage) {
|
|
55
|
+
await session.send('请输入要聊天的内容。');
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
// 构建消息列表
|
|
61
|
+
const messages = [
|
|
62
|
+
{ role: 'system', content: config.systemPrompt },
|
|
63
|
+
{ role: 'user', content: content },
|
|
64
|
+
];
|
|
65
|
+
// 调用 AI
|
|
66
|
+
const request = {
|
|
67
|
+
model: config.model,
|
|
68
|
+
messages,
|
|
69
|
+
temperature: config.temperature,
|
|
70
|
+
max_tokens: config.maxTokens,
|
|
71
|
+
top_p: config.topP,
|
|
72
|
+
frequency_penalty: config.frequencyPenalty,
|
|
73
|
+
presence_penalty: config.presencePenalty,
|
|
74
|
+
};
|
|
75
|
+
const response = await api.chatCompletion(request);
|
|
76
|
+
let finalResponse = response;
|
|
77
|
+
// 如果启用了情绪分析,则分析 AI 输出的内容
|
|
78
|
+
if (config.enableEmotion && emotionAnalyzer) {
|
|
79
|
+
const emotion = await emotionAnalyzer.analyzeEmotion(response);
|
|
80
|
+
const emotionImage = emotionAnalyzer.getEmotionImage(emotion);
|
|
81
|
+
if (emotion && emotionImage) {
|
|
82
|
+
finalResponse = `${response}\n\n${emotionImage}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// 发送回复
|
|
86
|
+
await session.send(finalResponse);
|
|
87
|
+
// 更新冷却时间
|
|
88
|
+
storage.setCooldown(userId, { lastRequestTime: Date.now() });
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error('AI 请求失败:', error);
|
|
92
|
+
if (config.enableMessage) {
|
|
93
|
+
await session.send(`请求失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
// 黑名单管理命令
|
|
98
|
+
ctx.command(`${config.commandName}.admin`)
|
|
99
|
+
.subcommand('.add <userId:string>', '添加黑名单')
|
|
100
|
+
.action(async ({ session }, userId) => {
|
|
101
|
+
if (!session)
|
|
102
|
+
return;
|
|
103
|
+
config.blacklist.push(userId);
|
|
104
|
+
await session.send(`已将用户 ${userId} 添加到黑名单。`);
|
|
105
|
+
});
|
|
106
|
+
ctx.command(`${config.commandName}.admin`)
|
|
107
|
+
.subcommand('.remove <userId:string>', '移除黑名单')
|
|
108
|
+
.action(async ({ session }, userId) => {
|
|
109
|
+
if (!session)
|
|
110
|
+
return;
|
|
111
|
+
const index = config.blacklist.indexOf(userId);
|
|
112
|
+
if (index > -1) {
|
|
113
|
+
config.blacklist.splice(index, 1);
|
|
114
|
+
await session.send(`已将用户 ${userId} 从黑名单中移除。`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
await session.send(`用户 ${userId} 不在黑名单中。`);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
ctx.command(`${config.commandName}.admin`)
|
|
121
|
+
.subcommand('.list', '查看黑名单')
|
|
122
|
+
.action(async ({ session }) => {
|
|
123
|
+
if (!session)
|
|
124
|
+
return;
|
|
125
|
+
if (config.blacklist.length === 0) {
|
|
126
|
+
await session.send('黑名单为空。');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
await session.send(`黑名单列表:\n${config.blacklist.join('\n')}`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
// 冷却时间管理命令
|
|
133
|
+
ctx.command(`${config.commandName}.admin`)
|
|
134
|
+
.subcommand('.clearcooldown <userId:string>', '清除用户冷却')
|
|
135
|
+
.action(async ({ session }, userId) => {
|
|
136
|
+
if (!session)
|
|
137
|
+
return;
|
|
138
|
+
storage.deleteCooldown(userId);
|
|
139
|
+
await session.send(`已清除用户 ${userId} 的冷却时间。`);
|
|
140
|
+
});
|
|
141
|
+
// 配置信息查看命令
|
|
142
|
+
ctx.command(`${config.commandName}.admin`)
|
|
143
|
+
.subcommand('.status', '查看插件状态')
|
|
144
|
+
.action(async ({ session }) => {
|
|
145
|
+
if (!session)
|
|
146
|
+
return;
|
|
147
|
+
const status = [
|
|
148
|
+
`模型: ${config.model}`,
|
|
149
|
+
`端点: ${config.endpoint}`,
|
|
150
|
+
`情绪分析: ${config.enableEmotion ? '已启用' : '未启用'}`,
|
|
151
|
+
`冷却时间: ${config.cooldown / 1000} 秒`,
|
|
152
|
+
`黑名单数量: ${config.blacklist.length}`,
|
|
153
|
+
];
|
|
154
|
+
await session.send(`插件状态:\n${status.join('\n')}`);
|
|
155
|
+
});
|
|
156
|
+
ctx.logger.info('koishi-plugin-openai-compatible 插件已加载');
|
|
157
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
commands: {
|
|
3
|
+
ai: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
input: string;
|
|
7
|
+
cooldown: string;
|
|
8
|
+
blacklist: string;
|
|
9
|
+
error: string;
|
|
10
|
+
};
|
|
11
|
+
admin: {
|
|
12
|
+
add: {
|
|
13
|
+
description: string;
|
|
14
|
+
success: string;
|
|
15
|
+
};
|
|
16
|
+
remove: {
|
|
17
|
+
description: string;
|
|
18
|
+
success: string;
|
|
19
|
+
notFound: string;
|
|
20
|
+
};
|
|
21
|
+
list: {
|
|
22
|
+
description: string;
|
|
23
|
+
empty: string;
|
|
24
|
+
title: string;
|
|
25
|
+
};
|
|
26
|
+
clearcooldown: {
|
|
27
|
+
description: string;
|
|
28
|
+
success: string;
|
|
29
|
+
};
|
|
30
|
+
status: {
|
|
31
|
+
description: string;
|
|
32
|
+
title: string;
|
|
33
|
+
model: string;
|
|
34
|
+
endpoint: string;
|
|
35
|
+
emotion: string;
|
|
36
|
+
enabled: string;
|
|
37
|
+
disabled: string;
|
|
38
|
+
cooldown: string;
|
|
39
|
+
blacklistCount: string;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export default _default;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = {
|
|
4
|
+
commands: {
|
|
5
|
+
ai: {
|
|
6
|
+
name: 'AI 对话',
|
|
7
|
+
description: '与 AI 进行对话',
|
|
8
|
+
input: '请输入要聊天的内容。',
|
|
9
|
+
cooldown: '请求过于频繁,请等待 {0} 秒后再试。',
|
|
10
|
+
blacklist: '您在黑名单中,无法使用此功能。',
|
|
11
|
+
error: '请求失败: {0}',
|
|
12
|
+
},
|
|
13
|
+
admin: {
|
|
14
|
+
add: {
|
|
15
|
+
description: '添加黑名单',
|
|
16
|
+
success: '已将用户 {0} 添加到黑名单。',
|
|
17
|
+
},
|
|
18
|
+
remove: {
|
|
19
|
+
description: '移除黑名单',
|
|
20
|
+
success: '已将用户 {0} 从黑名单中移除。',
|
|
21
|
+
notFound: '用户 {0} 不在黑名单中。',
|
|
22
|
+
},
|
|
23
|
+
list: {
|
|
24
|
+
description: '查看黑名单',
|
|
25
|
+
empty: '黑名单为空。',
|
|
26
|
+
title: '黑名单列表:',
|
|
27
|
+
},
|
|
28
|
+
clearcooldown: {
|
|
29
|
+
description: '清除用户冷却',
|
|
30
|
+
success: '已清除用户 {0} 的冷却时间。',
|
|
31
|
+
},
|
|
32
|
+
status: {
|
|
33
|
+
description: '查看插件状态',
|
|
34
|
+
title: '插件状态:',
|
|
35
|
+
model: '模型: {0}',
|
|
36
|
+
endpoint: '端点: {0}',
|
|
37
|
+
emotion: '情绪分析: {0}',
|
|
38
|
+
enabled: '已启用',
|
|
39
|
+
disabled: '未启用',
|
|
40
|
+
cooldown: '冷却时间: {0} 秒',
|
|
41
|
+
blacklistCount: '黑名单数量: {0}',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
package/lib/storage.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface CooldownData {
|
|
2
|
+
lastRequestTime: number;
|
|
3
|
+
}
|
|
4
|
+
export declare class Storage {
|
|
5
|
+
private cooldowns;
|
|
6
|
+
getCooldown(userId: string): CooldownData | undefined;
|
|
7
|
+
setCooldown(userId: string, data: CooldownData): void;
|
|
8
|
+
deleteCooldown(userId: string): void;
|
|
9
|
+
isOnCooldown(userId: string, cooldownTime: number): boolean;
|
|
10
|
+
}
|
package/lib/storage.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Storage = void 0;
|
|
4
|
+
class Storage {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.cooldowns = new Map();
|
|
7
|
+
}
|
|
8
|
+
getCooldown(userId) {
|
|
9
|
+
return this.cooldowns.get(userId);
|
|
10
|
+
}
|
|
11
|
+
setCooldown(userId, data) {
|
|
12
|
+
this.cooldowns.set(userId, data);
|
|
13
|
+
}
|
|
14
|
+
deleteCooldown(userId) {
|
|
15
|
+
this.cooldowns.delete(userId);
|
|
16
|
+
}
|
|
17
|
+
isOnCooldown(userId, cooldownTime) {
|
|
18
|
+
const data = this.getCooldown(userId);
|
|
19
|
+
if (!data)
|
|
20
|
+
return false;
|
|
21
|
+
const elapsed = Date.now() - data.lastRequestTime;
|
|
22
|
+
return elapsed < cooldownTime;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.Storage = Storage;
|