-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCommandHandler.cs
More file actions
403 lines (369 loc) · 17.9 KB
/
CommandHandler.cs
File metadata and controls
403 lines (369 loc) · 17.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
using System;
using System.Linq;
using TShockAPI;
namespace AIAgent;
/// <summary>
/// 命令处理器 - 处理所有 /aig 子命令
/// </summary>
public static class CommandHandler
{
#region 帮助信息
public static void SendHelp(TSPlayer plr)
{
plr.SendInfoMessage("[c/00FF00:========== AIAgent 智能助手 ==========]");
plr.SendInfoMessage("[c/FFD700:基础对话:]");
plr.SendInfoMessage(" /aig <内容> - 向AI发送简短问题");
plr.SendInfoMessage(" /aig say <内容> - 向AI发送长文本内容");
plr.SendInfoMessage("[c/FFD700:命令管理:]");
plr.SendInfoMessage(" /aig <编号> yes/no - 确认或拒绝AI建议的命令");
plr.SendInfoMessage(" /aig auto yes/no - 开启或关闭自动执行模式");
plr.SendInfoMessage("[c/FFD700:模型管理:]");
plr.SendInfoMessage(" /aig model list - 查看支持的AI模型列表");
plr.SendInfoMessage(" /aig model test - 测试当前模型连接状态");
plr.SendInfoMessage("[c/FFD700:个人数据:]");
plr.SendInfoMessage(" /aig stats - 查看你的Token消耗统计");
if (plr.HasPermission("aiagent.admin"))
{
plr.SendInfoMessage("[c/FF6B6B:管理员命令:]");
plr.SendInfoMessage(" /aig set name <名字> - 设置AI显示名称");
plr.SendInfoMessage(" /aig set apiurl <地址> - 设置API接口地址");
plr.SendInfoMessage(" /aig set key <密钥> - 设置API访问密钥");
plr.SendInfoMessage(" /aig set model <模型> - 设置AI模型名称");
plr.SendInfoMessage(" /aig set maxlength <字数> - 设置AI最大回复字数(0=不限)");
plr.SendInfoMessage(" /aig set maxinput <字数> - 设置玩家最大输入字数(0=不限)");
plr.SendInfoMessage(" /aig set mode <truncate/limit> - 设置字数超限处理方式");
plr.SendInfoMessage(" /aig set stream yes/no - 开启或关闭流式传输");
plr.SendInfoMessage(" /aig set simple yes/no - 开启或关闭极简模式");
plr.SendInfoMessage(" /aig set websearch yes/no - 开启或关闭联网搜索");
plr.SendInfoMessage(" /aig set memory yes/no - 开启或关闭上下文记忆");
plr.SendInfoMessage(" /aig persona set <人设> - 设置AI人设");
plr.SendInfoMessage(" /aig persona strict <1-10> - 设置人设遵守严格度");
plr.SendInfoMessage(" /aig persona view - 查看当前人设");
plr.SendInfoMessage(" /aig whitelist add <玩家> - 添加白名单");
plr.SendInfoMessage(" /aig whitelist remove <玩家> - 移除白名单");
plr.SendInfoMessage(" /aig whitelist list - 查看白名单");
plr.SendInfoMessage(" /aig stats all - 查看全服Token统计");
plr.SendInfoMessage(" /aig clearstats - 清空所有统计");
}
plr.SendInfoMessage("[c/808080:========================================]");
}
#endregion
#region 设置命令
public static void HandleSet(TSPlayer plr, CommandArgs args)
{
if (!plr.HasPermission("aiagent.admin"))
{
plr.SendErrorMessage("[AIAgent] 你没有权限修改配置,需要 aiagent.admin 权限。");
return;
}
var key = args.Parameters[1].ToLower();
var val = args.Parameters[2];
switch (key)
{
case "name":
PluginState.Config.AIName = val;
plr.SendSuccessMessage($"[AIAgent] AI名称已更新为 \"{val}\"");
break;
case "apiurl":
PluginState.Config.BaseUrl = val.TrimEnd('/');
plr.SendSuccessMessage($"[AIAgent] API地址已更新为 \"{PluginState.Config.BaseUrl}\"");
break;
case "key":
PluginState.Config.ApiKey = val;
plr.SendSuccessMessage("[AIAgent] API密钥已更新。");
break;
case "model":
PluginState.Config.Model = val;
plr.SendSuccessMessage($"[AIAgent] AI模型已更新为 \"{val}\"");
break;
case "maxlength":
if (int.TryParse(val, out var maxLen) && maxLen >= 0)
{
PluginState.Config.MaxResponseLength = maxLen;
plr.SendSuccessMessage($"[AIAgent] AI最大回复字数已更新为 {(maxLen == 0 ? "无限制" : maxLen + "字")}");
}
else
plr.SendErrorMessage("[AIAgent] 无效数值,请输入大于等于0的整数。");
break;
case "maxinput":
if (int.TryParse(val, out var maxIn) && maxIn >= 0)
{
PluginState.Config.MaxInputLength = maxIn;
plr.SendSuccessMessage($"[AIAgent] 玩家最大输入字数已更新为 {(maxIn == 0 ? "无限制" : maxIn + "字")}");
}
else
plr.SendErrorMessage("[AIAgent] 无效数值,请输入大于等于0的整数。");
break;
case "mode":
var mode = val.ToLower();
if (mode is "truncate" or "limit")
{
PluginState.Config.ResponseLimitMode = mode;
plr.SendSuccessMessage($"[AIAgent] 字数超限处理方式已更新为 \"{(mode == "truncate" ? "截断展示" : "严格限制")}\"");
}
else
plr.SendErrorMessage("[AIAgent] 无效模式,请使用 truncate 或 limit。");
break;
case "stream":
HandleToggle(plr, val, "流式传输", v => PluginState.Config.EnableStream = v,
true, "流式模式: AI逐字返回,体验更好", "非流式模式: 等待完整返回,兼容性更好");
break;
case "simple":
HandleToggle(plr, val, "极简模式", v => PluginState.Config.SimpleMode = v,
true, "极简模式: 只显示AI对话输出", "完整模式: 显示所有辅助信息");
break;
case "websearch":
var wsVal = val.ToLower();
if (wsVal is "yes" or "no")
{
var wsEnabled = wsVal == "yes";
PluginState.Config.EnableWebSearch = wsEnabled;
plr.SendSuccessMessage($"[AIAgent] 联网搜索已{(wsEnabled ? "开启" : "关闭")}");
if (wsEnabled)
{
var model = PluginState.Config.Model;
if (model.ToLower().Contains("gpt-4") || model.ToLower().Contains("claude"))
plr.SendInfoMessage("联网搜索已开启: 当前模型支持此功能。");
else
{
plr.SendInfoMessage($"联网搜索已开启,但当前模型 \"{model}\" 可能不支持此功能。");
plr.SendInfoMessage("支持联网搜索的模型: gpt-4系列、claude系列。");
}
}
}
else
plr.SendErrorMessage("[AIAgent] 无效值,请使用 yes 或 no。");
break;
case "memory":
HandleToggle(plr, val, "上下文记忆", v => PluginState.Config.EnableContextMemory = v,
true, "上下文记忆已开启: AI会记住之前的对话", "上下文记忆已关闭: 每次对话独立");
break;
default:
plr.SendErrorMessage("[AIAgent] 未知设置项,可用: name, apiurl, key, model, maxlength, maxinput, mode, stream, simple, websearch, memory");
return;
}
ConfigManager.SaveConfig();
}
private static void HandleToggle(TSPlayer plr, string val, string name, Action<bool> setter,
bool defaultShowInfo, string onDesc, string offDesc)
{
var v = val.ToLower();
if (v is "yes" or "no")
{
var enabled = v == "yes";
setter(enabled);
plr.SendSuccessMessage($"[AIAgent] {name}已{(enabled ? "开启" : "关闭")}");
if (defaultShowInfo)
plr.SendInfoMessage(enabled ? onDesc : offDesc);
}
else
plr.SendErrorMessage("[AIAgent] 无效值,请使用 yes 或 no。");
}
#endregion
#region 确认/拒绝
public static void HandleConfirm(TSPlayer plr, string sub, int id)
{
if (!PluginState.Pending.TryGetValue(id, out var req) || req.PlayerName != plr.Name)
{
plr.SendErrorMessage("[AIAgent] 无效的请求编号,该请求可能已过期或不是你的请求。");
return;
}
PluginState.Pending.TryRemove(id, out _);
if (sub == "yes")
{
plr.SendSuccessMessage($"[AIAgent] 你确认了执行 [{id}] 的命令请求。");
ChatUtils.ExecuteCommands(plr, req.Commands, id);
}
else
{
plr.SendInfoMessage($"[AIAgent] 你拒绝了 [{id}] 的命令执行请求,已取消。");
}
}
#endregion
#region 模型管理
public static void HandleModel(TSPlayer plr, CommandArgs args)
{
if (args.Parameters.Count < 2)
{
plr.SendInfoMessage("[AIAgent] 用法: /aig model list 或 /aig model test [fast]");
return;
}
var action = args.Parameters[1].ToLower();
if (action == "list")
{
plr.SendInfoMessage("[c/00FF00:========== 支持的AI模型 ==========]");
plr.SendInfoMessage("[c/FFD700:Moonshot Kimi:]");
plr.SendInfoMessage(" moonshot-v1-8k - 轻量级对话(8K上下文)");
plr.SendInfoMessage(" moonshot-v1-32k - 标准对话(32K上下文)");
plr.SendInfoMessage(" moonshot-v1-128k - 长文档分析(128K上下文)");
plr.SendInfoMessage("[c/FFD700:DeepSeek:]");
plr.SendInfoMessage(" deepseek-chat - 通用对话(性价比首选)");
plr.SendInfoMessage(" deepseek-coder - 代码与逻辑分析");
plr.SendInfoMessage(" deepseek-reasoner- 深度推理(R1模型)");
plr.SendInfoMessage("[c/FFD700:OpenAI:]");
plr.SendInfoMessage(" gpt-4o-mini - 轻量快速");
plr.SendInfoMessage(" gpt-4o - 全能旗舰");
plr.SendInfoMessage("[c/FFD700:其他:]");
plr.SendInfoMessage(" Claude需中转网关 | Ollama本地部署");
if (!PluginState.Config.SimpleMode)
{
plr.SendInfoMessage("[c/808080:注意: API地址需包含/v1路径]");
plr.SendInfoMessage("[c/808080: 例: https://ai.xem8k5.top/v1]");
}
plr.SendInfoMessage("[c/808080:========================================]");
}
else if (action == "test")
{
var fast = args.Parameters.Count > 2 && args.Parameters[2] == "fast";
_ = AIHandler.TestModel(plr, fast);
}
}
#endregion
#region Token统计
public static void HandleStats(TSPlayer plr, CommandArgs args)
{
if (args.Parameters.Count > 1 && args.Parameters[1].ToLower() == "all")
{
if (!plr.HasPermission("aiagent.admin"))
{
plr.SendErrorMessage("[AIAgent] 你没有权限查看全局统计,需要 aiagent.admin 权限。");
return;
}
plr.SendInfoMessage("[c/00FF00:========== 全服Token消耗统计 ==========]");
if (PluginState.TokenStats.IsEmpty)
{
plr.SendInfoMessage("暂无数据记录。");
return;
}
foreach (var s in PluginState.TokenStats.Values.OrderByDescending(x => x.TotalTokens))
plr.SendInfoMessage($" {s.PlayerName}: {s.TotalTokens:N0} tokens (请求{s.RequestCount}次)");
var grandTotal = PluginState.TokenStats.Values.Sum(x => x.TotalTokens);
plr.SendInfoMessage($"[c/FFD700:全服总计: {grandTotal:N0} tokens]");
}
else
{
if (PluginState.TokenStats.TryGetValue(plr.Name, out var myStats))
{
plr.SendInfoMessage("[c/00FF00:========== 你的Token消耗统计 ==========]");
plr.SendInfoMessage($" 请求次数: {myStats.RequestCount} 次");
plr.SendInfoMessage($" 输入消耗: {myStats.PromptTokens:N0} tokens");
plr.SendInfoMessage($" 输出消耗: {myStats.CompletionTokens:N0} tokens");
plr.SendInfoMessage($" 累计消耗: {myStats.TotalTokens:N0} tokens");
plr.SendInfoMessage($" 首次使用: {myStats.FirstUsed:yyyy年MM月dd日 HH:mm}");
plr.SendInfoMessage($" 最后使用: {myStats.LastUsed:yyyy年MM月dd日 HH:mm}");
}
else
{
plr.SendInfoMessage("[AIAgent] 你还没有使用记录,发送一条消息给AI后开始统计。");
}
}
}
#endregion
#region 白名单管理
public static void HandleWhitelist(TSPlayer plr, CommandArgs args)
{
if (!plr.HasPermission("aiagent.admin"))
{
plr.SendErrorMessage("[AIAgent] 你没有权限管理白名单,需要 aiagent.admin 权限。");
return;
}
if (args.Parameters.Count < 2)
{
plr.SendInfoMessage("[AIAgent] 用法: /aig whitelist add/remove/list <玩家>");
return;
}
var action = args.Parameters[1].ToLower();
if (action == "list")
{
plr.SendInfoMessage("[c/00FF00:========== AI使用白名单 ==========]");
var list = PluginState.Config.AllowedPlayers;
if (list == null || list.Count == 0)
plr.SendInfoMessage("白名单为空,当前允许所有玩家使用AI功能。");
else
{
plr.SendInfoMessage($"白名单玩家 ({list.Count}人):");
plr.SendInfoMessage(" " + string.Join(", ", list));
}
}
else if (args.Parameters.Count >= 3)
{
var target = args.Parameters[2];
PluginState.Config.AllowedPlayers ??= new System.Collections.Generic.List<string>();
if (action == "add")
{
if (!PluginState.Config.AllowedPlayers.Contains(target))
{
PluginState.Config.AllowedPlayers.Add(target);
plr.SendSuccessMessage($"[AIAgent] 已将玩家 \"{target}\" 添加到白名单。");
}
else
plr.SendInfoMessage($"[AIAgent] 玩家 \"{target}\" 已在白名单中。");
}
else if (action == "remove")
{
if (PluginState.Config.AllowedPlayers.Remove(target))
plr.SendSuccessMessage($"[AIAgent] 已将玩家 \"{target}\" 从白名单移除。");
else
plr.SendInfoMessage($"[AIAgent] 玩家 \"{target}\" 不在白名单中。");
}
ConfigManager.SaveConfig();
}
}
#endregion
#region 人设管理
public static void HandlePersona(TSPlayer plr, CommandArgs args)
{
if (!plr.HasPermission("aiagent.admin"))
{
plr.SendErrorMessage("[AIAgent] 你没有权限管理人设,需要 aiagent.admin 权限。");
return;
}
if (args.Parameters.Count < 2)
{
plr.SendInfoMessage("[AIAgent] 用法: /aig persona set <人设描述> | strict <1-10> | view");
return;
}
var action = args.Parameters[1].ToLower();
if (action == "set" && args.Parameters.Count >= 3)
{
var persona = string.Join(" ", args.Parameters.Skip(2));
PluginState.Config.Persona = persona;
ConfigManager.SaveConfig();
plr.SendSuccessMessage("[AIAgent] AI人设已更新。");
plr.SendInfoMessage($"当前人设: \"{persona}\"");
}
else if (action == "strict" && args.Parameters.Count >= 3)
{
if (int.TryParse(args.Parameters[2], out var strict) && strict >= 1 && strict <= 10)
{
PluginState.Config.PersonaStrictness = strict;
ConfigManager.SaveConfig();
var level = strict switch
{
<= 3 => "宽松(AI可在人设基础上自由发挥)",
<= 6 => "适中(AI需遵循人设但可灵活应对)",
<= 8 => "严格(AI必须严格遵守人设设定)",
_ => "极度严格(AI完全按照人设行动,不可偏离)"
};
plr.SendSuccessMessage($"[AIAgent] 人设遵守严格度已设置为 {strict}/10");
plr.SendInfoMessage($"严格度说明: {level}");
}
else
plr.SendErrorMessage("[AIAgent] 严格度必须是1到10之间的整数。");
}
else if (action == "view")
{
plr.SendInfoMessage("[c/00FF00:========== 当前AI人设 ==========]");
if (string.IsNullOrWhiteSpace(PluginState.Config.Persona))
plr.SendInfoMessage("当前未设置人设,AI将以默认助手模式运行。");
else
plr.SendInfoMessage($"人设描述: \"{PluginState.Config.Persona}\"");
var level = PluginState.Config.PersonaStrictness switch
{
<= 3 => "宽松", <= 6 => "适中", <= 8 => "严格", _ => "极度严格"
};
plr.SendInfoMessage($"遵守严格度: {PluginState.Config.PersonaStrictness}/10 ({level})");
}
}
#endregion
}