alemonjs-aichat 1.0.15 → 1.0.16
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 +14 -0
- package/lib/api/aitools.js +620 -0
- package/lib/api/format.js +13 -0
- package/lib/api/loadSkill.js +70 -0
- package/lib/api/review.js +66 -0
- package/lib/api/tts.js +144 -0
- package/lib/assets/error-DJzYQZ11.png +0 -0
- package/lib/assets/error.png.js +4 -0
- package/lib/config.js +292 -323
- package/lib/data/help.json.js +24 -8
- package/lib/index.js +9 -3
- package/lib/middleware/mw.js +53 -51
- package/lib/redis.js +1 -1
- package/lib/response/affection/res.js +7 -11
- package/lib/response/config/res.js +19 -25
- package/lib/response/help/res.js +3 -4
- package/lib/response/ollama/ollama.js +42 -0
- package/lib/response/setting/res.js +125 -86
- package/lib/response/tools/res.js +82 -62
- package/lib/response/zreply/res.js +110 -38
- package/lib/routes/commands.js +43 -0
- package/lib/routes/middleware.js +11 -0
- package/lib/s3.js +3 -3
- package/package.json +7 -23
package/README.md
CHANGED
|
@@ -31,6 +31,20 @@ yarn dev
|
|
|
31
31
|
## 打包
|
|
32
32
|
yarn build
|
|
33
33
|
```
|
|
34
|
+
### 从零开始安装
|
|
35
|
+
|
|
36
|
+
1. 拉取仓库
|
|
37
|
+
```sh
|
|
38
|
+
git clone -b release git@gitee.com:suancaixianyu/alemonjs-aichat.git
|
|
39
|
+
cd alemonjs-aichat
|
|
40
|
+
yarn
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
2. 配置`alemon.config.yaml`连接平台
|
|
44
|
+
3. 启动
|
|
45
|
+
```sh
|
|
46
|
+
node index
|
|
47
|
+
```
|
|
34
48
|
|
|
35
49
|
## 2.配置
|
|
36
50
|
|
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { redis } from '../redis.js';
|
|
2
|
+
import { getConfigValue } from 'alemonjs';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { uploadImageToR2 } from '../s3.js';
|
|
5
|
+
import { TTSClient } from './tts.js';
|
|
6
|
+
import { loadSkillDetail } from './loadSkill.js';
|
|
7
|
+
|
|
8
|
+
const value = getConfigValue().aiChat || {};
|
|
9
|
+
const getPrompt = async (text) => {
|
|
10
|
+
// 检查中文
|
|
11
|
+
const hasChinese = /[\u4e00-\u9fa5]/g.test(text);
|
|
12
|
+
const fanyiApi = "https://api.52vmy.cn/api/query/fanyi/youdao?msg=";
|
|
13
|
+
if (hasChinese) {
|
|
14
|
+
try {
|
|
15
|
+
// 调用后端的 send_get_request 函数
|
|
16
|
+
const responseText = await fetch(fanyiApi + encodeURIComponent(text)).then((res) => res.text());
|
|
17
|
+
// 将返回的 JSON 字符串解析为对象
|
|
18
|
+
const data = JSON.parse(responseText);
|
|
19
|
+
console.log("提示词翻译:", data.data.target);
|
|
20
|
+
return data.data.target;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error("Error fetching translation:", error);
|
|
24
|
+
return text; // 如果翻译失败,返回原始文本
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const tools = [
|
|
32
|
+
{
|
|
33
|
+
type: "function",
|
|
34
|
+
function: {
|
|
35
|
+
name: "StableDiffusionGenerateImage",
|
|
36
|
+
description: "使用 Stable Diffusion 模型生成图像",
|
|
37
|
+
parameters: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
prompt: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "正向提示词(prompt),越详细越好。建议包含:主体、动作、服装、场景、风格、光线、画质修饰词。\n推荐全英文且使用标签描述,当遇到动漫人物时,使用ta的英文名可以自动触发lora,不用额外lora标签\n括号的使用:[增强效果],(弱化效果), 示例:'[artist:ciloranko],artist:kazutake_hazano,[artist:kedama milk],smile, short_hair, open_mouth, bangs, multiple_girls, skirt, black_hair, hair_ornament, red_eyes, long_sleeves, 2girls, sitting, school_uniform, :d, grey_hair, multicolored_hair, pleated_skirt, shoes, serafuku, choker, socks, sailor_collar, neckerchief, orange_eyes, petals, siblings, black_choker, brown_footwear, blue sky, pointing, loafers, red_neckerchief, bench, falling_petals'",
|
|
43
|
+
},
|
|
44
|
+
bad_prompt: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "反向提示词(negative prompt),用于排除不想要的元素。\n示例:'lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, deformed'",
|
|
47
|
+
default: "(easynegative:1.1), (verybadimagenegative_v1.3:1), (low quality:1.2), (worst quality:1.2)",
|
|
48
|
+
},
|
|
49
|
+
direction: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "图片方向, 横向: landscape, 纵向: portrait, 方形: square",
|
|
52
|
+
enum: ["landscape", "portrait", "square"],
|
|
53
|
+
default: "portrait",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ["prompt"],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: "function",
|
|
62
|
+
function: {
|
|
63
|
+
name: "StableDiffusionImageToPrompt",
|
|
64
|
+
description: "使用 Stable Diffusion 将图片转换为提示词, 它只对动漫图片有效, 只能用来提取动漫图片的提示词",
|
|
65
|
+
parameters: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
image_url: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "图片的URL地址,必须是你所收到的图片地址",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ["image_url"],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
// {
|
|
78
|
+
// type: "function",
|
|
79
|
+
// function: {
|
|
80
|
+
// name: "SearchScbbsPost",
|
|
81
|
+
// description:
|
|
82
|
+
// "搜索《survivalcraft》(译名《生存战争》,也简称sc)论坛的帖子, 搜索时请尽量提供精确的关键词, 关键词最好只有一个. 在这里可以搜索到sc这个游戏相关的各种模组等资源或教程帖子和更新公告等内容, 但请注意不要搜索与游戏无关的内容, 否则可能会得到不相关的结果,若是搜索不到请减少关键词的数量或更换关键词",
|
|
83
|
+
// parameters: {
|
|
84
|
+
// type: "object",
|
|
85
|
+
// properties: {
|
|
86
|
+
// keyword: {
|
|
87
|
+
// type: "string",
|
|
88
|
+
// description: "搜索关键词",
|
|
89
|
+
// },
|
|
90
|
+
// },
|
|
91
|
+
// required: ["keyword"],
|
|
92
|
+
// },
|
|
93
|
+
// },
|
|
94
|
+
// },
|
|
95
|
+
{
|
|
96
|
+
type: "function",
|
|
97
|
+
function: {
|
|
98
|
+
name: "SearchWeb",
|
|
99
|
+
description: "使用搜索引擎搜索网页内容, 你可以使用这个工具来获取最新的资讯或数据, 但请注意不要搜索敏感或违法的内容, 否则可能会得到不相关的结果, 此外,不推荐使用该工具搜索与sc这个游戏相关的内容, 因为搜索引擎的结果可能不如专门的游戏论坛或社区来的准确和有用, 在搜索时请尽量提供精确的关键词, 关键词最好只有一个. 如果你想搜索与游戏相关的内容, 建议使用SearchScbbsPost工具来搜索survivalcraft(生存战争)论坛的帖子",
|
|
100
|
+
parameters: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
keyword: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "搜索关键词",
|
|
106
|
+
},
|
|
107
|
+
engine: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "选择搜索引擎",
|
|
110
|
+
enum: ["bing", "google", "baidu"],
|
|
111
|
+
default: "baidu",
|
|
112
|
+
},
|
|
113
|
+
cc: {
|
|
114
|
+
type: "string",
|
|
115
|
+
description: "搜索国家/地区代码, 例如搜索美国就填us, 搜索中国就填cn, 搜索日本就填jp, 这个参数会影响搜索结果的地域性",
|
|
116
|
+
default: "CN",
|
|
117
|
+
},
|
|
118
|
+
type: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "选择结果类型, 搜索后获取对应结果, 建议使用自然结果organic_results, 其他选项可能不存在",
|
|
121
|
+
enum: ["organic_results", "related_questions", "related_searches"],
|
|
122
|
+
default: "organic_results",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ["keyword"],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
type: "function",
|
|
131
|
+
function: {
|
|
132
|
+
name: "Photoshop",
|
|
133
|
+
description: "AI修图工具",
|
|
134
|
+
parameters: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
image_url: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "需要修改的图片URL地址,必须是你所收到的图片地址",
|
|
140
|
+
},
|
|
141
|
+
instruction: {
|
|
142
|
+
type: "string",
|
|
143
|
+
description: "修改需求的文本描述,尽量详细和具体,例如:'请将图片中的人物换成一个穿着宇航服的宇航员,背景改成月球表面,保持其他元素不变'",
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
required: ["instruction"],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: "function",
|
|
152
|
+
function: {
|
|
153
|
+
name: "GetTTSModels",
|
|
154
|
+
description: "获取可用的TTS模型列表, 可以用来选择不同的音色进行语音合成. 模型会不定期更新, 你可以定期调用这个工具来获取最新的模型列表. 当你想要使用TTS功能时, 可以先调用这个工具来获取可用的模型列表, 然后选择一个你喜欢的模型来进行语音合成. 例如, 你可以选择一个叫做'派蒙-默认'的模型来模仿派蒙的声音进行语音合成",
|
|
155
|
+
parameters: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
type: "function",
|
|
163
|
+
function: {
|
|
164
|
+
name: "RunCommand",
|
|
165
|
+
description: "执行终端命令, 请勿执行高危险性指令, 例如rm -rf /等, 否则可能会导致服务器数据丢失或系统崩溃. 你可以使用这个工具来执行一些简单的命令, 例如查看当前目录下的文件ls, 查看系统状态top等. 当你想要执行一个命令时, 请提供具体的命令文本, 例如'ls -la'或'top -n 1'. 执行结果将会返回给你, 但请注意, 由于安全限制, 某些命令可能无法执行或返回受限的结果",
|
|
166
|
+
parameters: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
command: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "要执行的终端命令",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
required: ["command"],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
type: "function",
|
|
180
|
+
function: {
|
|
181
|
+
name: "getSKILLDetail",
|
|
182
|
+
description: "获取技能详情",
|
|
183
|
+
parameters: {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties: {
|
|
186
|
+
skillName: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "要获取详情的技能名称",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
required: ["skillName"],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
const availableTools = {
|
|
197
|
+
/**
|
|
198
|
+
* 使用 Stable Diffusion 生成图片
|
|
199
|
+
* @param {string} prompt - 正向提示词
|
|
200
|
+
* @param {string} [bad_prompt] - 反向提示词
|
|
201
|
+
* @param {string} [direction] - 方向: landscape, portrait, square
|
|
202
|
+
* @param {boolean} [base64] - 是否返回base64格式的图片
|
|
203
|
+
* @returns {Promise<{type: "image", url: string}[]>} 图片列表
|
|
204
|
+
*/
|
|
205
|
+
StableDiffusionGenerateImage: async ({ prompt, bad_prompt = "(easynegative:1.1), (verybadimagenegative_v1.3:1), (low quality:1.2), (worst quality:1.2)", direction = "portrait", base64 = false, }) => {
|
|
206
|
+
const images = [];
|
|
207
|
+
const sizeMap = {
|
|
208
|
+
landscape: { width: 768, height: 512 },
|
|
209
|
+
portrait: { width: 512, height: 768 },
|
|
210
|
+
square: { width: 640, height: 640 },
|
|
211
|
+
};
|
|
212
|
+
const { width, height } = sizeMap[direction] || sizeMap["portrait"];
|
|
213
|
+
// 检查是否有中文
|
|
214
|
+
const body = {
|
|
215
|
+
enable_hr: true,
|
|
216
|
+
denoising_strength: 0.8,
|
|
217
|
+
hr_scale: 2,
|
|
218
|
+
hr_upscaler: "R-ESRGAN 4x+ Anime6B",
|
|
219
|
+
hr_second_pass_steps: 10,
|
|
220
|
+
prompt: await getPrompt(prompt),
|
|
221
|
+
seed: -1,
|
|
222
|
+
sampler_name: "DPM++ 2M",
|
|
223
|
+
steps: 20,
|
|
224
|
+
cfg_scale: 10,
|
|
225
|
+
width: width,
|
|
226
|
+
height: height,
|
|
227
|
+
negative_prompt: bad_prompt,
|
|
228
|
+
};
|
|
229
|
+
try {
|
|
230
|
+
const response = await fetch("http://datukuai.top:1450/ht2.php", {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: {
|
|
233
|
+
"Content-Type": "application/json",
|
|
234
|
+
},
|
|
235
|
+
body: JSON.stringify(body),
|
|
236
|
+
});
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
return images;
|
|
239
|
+
}
|
|
240
|
+
const data = await response.json();
|
|
241
|
+
// 将图片保存到本地,然后返回本地路径
|
|
242
|
+
const dir = "./public/image_out";
|
|
243
|
+
if (!fs.existsSync(dir)) {
|
|
244
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
245
|
+
}
|
|
246
|
+
const path = `${dir}/${Date.now()}.jpg`;
|
|
247
|
+
const base64Data = data.images[0].replace(/^data:image\/\w+;base64,/, "");
|
|
248
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
249
|
+
fs.writeFileSync(path, buffer);
|
|
250
|
+
if (base64) {
|
|
251
|
+
return data.images;
|
|
252
|
+
}
|
|
253
|
+
images.push({ type: "image", url: path });
|
|
254
|
+
return images;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
console.error("Error generating images:", error);
|
|
258
|
+
return images;
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
/** 使用 Stable Diffusion 将图片转换为提示词 */
|
|
262
|
+
StableDiffusionImageToPrompt: async ({ image_url }) => {
|
|
263
|
+
// 先查看redis中是否有这张图的缓存
|
|
264
|
+
const cacheKey = `image:caption:${image_url}`;
|
|
265
|
+
const cachedCaption = await redis.get(cacheKey);
|
|
266
|
+
let imgBase64 = "";
|
|
267
|
+
if (cachedCaption) {
|
|
268
|
+
// 如果有, 它是本地路径, 获取base64
|
|
269
|
+
const data = fs.readFileSync(cachedCaption, { encoding: "base64" });
|
|
270
|
+
imgBase64 = data;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
// 如果没有, 下载图片并转换为base64
|
|
274
|
+
const response = await fetch(image_url);
|
|
275
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
276
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
277
|
+
imgBase64 = buffer.toString("base64");
|
|
278
|
+
}
|
|
279
|
+
const api = "http://datukuai.top:1450//ckmf.php";
|
|
280
|
+
const body = {
|
|
281
|
+
fn_index: 280,
|
|
282
|
+
data: [
|
|
283
|
+
"data:image/png;base64," + imgBase64,
|
|
284
|
+
"",
|
|
285
|
+
false,
|
|
286
|
+
"",
|
|
287
|
+
"[name].[output_extension]",
|
|
288
|
+
"ignore",
|
|
289
|
+
false,
|
|
290
|
+
"wd14-vit",
|
|
291
|
+
0.35,
|
|
292
|
+
"",
|
|
293
|
+
"",
|
|
294
|
+
false,
|
|
295
|
+
false,
|
|
296
|
+
true,
|
|
297
|
+
"0_0, (o)_(o), +_+, +_-, ._., <o>_<o>, <|>_<|>, =_=, >_<, 3_3, 6_9, >_o, @_@, ^_^, o_o, u_u, x_x, |_|, ||_||",
|
|
298
|
+
false,
|
|
299
|
+
false,
|
|
300
|
+
],
|
|
301
|
+
session_hash: "0yzsdmhe9br9",
|
|
302
|
+
};
|
|
303
|
+
const res = await fetch(api, {
|
|
304
|
+
method: "POST",
|
|
305
|
+
headers: {
|
|
306
|
+
"Content-Type": "application/json",
|
|
307
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
|
|
308
|
+
},
|
|
309
|
+
body: JSON.stringify(body),
|
|
310
|
+
});
|
|
311
|
+
if (!res.ok) {
|
|
312
|
+
return "无法转为提示词";
|
|
313
|
+
}
|
|
314
|
+
const data = await res.json();
|
|
315
|
+
const prompt = data.data[0];
|
|
316
|
+
console.log("图片提示词:", prompt);
|
|
317
|
+
return prompt;
|
|
318
|
+
},
|
|
319
|
+
/** 搜索survivalcraft(生存战争)论坛的帖子 */
|
|
320
|
+
SearchScbbsPost: async ({ keyword }) => {
|
|
321
|
+
const api = `https://m.suancaixianyu.cn/api/post/list?title=${encodeURIComponent(keyword)}`;
|
|
322
|
+
try {
|
|
323
|
+
const response = await fetch(api);
|
|
324
|
+
if (!response.ok) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
const data = await response.json();
|
|
328
|
+
return data.data.list.map((item) => ({
|
|
329
|
+
viewUrl: `https://test.suancaixianyu.cn/#/postDetails/${item.id}`,
|
|
330
|
+
title: item.title,
|
|
331
|
+
content: item.content,
|
|
332
|
+
userName: item.creator.nickname,
|
|
333
|
+
userPage: `https://test.suancaixianyu.cn/#/user/${item.creatorId}`,
|
|
334
|
+
createdAt: item.createdAt,
|
|
335
|
+
plate: item.plate.name,
|
|
336
|
+
postVersions: item.postVersions.map((version) => ({
|
|
337
|
+
version: version.version,
|
|
338
|
+
versionName: version.title,
|
|
339
|
+
createdAt: version.createdAt,
|
|
340
|
+
files: version.files.map((file) => ({
|
|
341
|
+
name: file.name,
|
|
342
|
+
url: file.url,
|
|
343
|
+
})),
|
|
344
|
+
})),
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
console.error("Error searching posts:", error);
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
/** 使用搜索引擎搜索网页 */
|
|
353
|
+
SearchWeb: async ({ keyword, engine = "baidu", cc = "CN", type = "organic_results", }) => {
|
|
354
|
+
const apikey = value.searchApiKey ||
|
|
355
|
+
"63706fc3e6827a90e8219913297b510ff053d5238b24f64251a066e4ecea7408";
|
|
356
|
+
const api = `https://serpapi.com/search.json?engine=${engine}&q=${encodeURIComponent(keyword)}&cc=${cc}&api_key=${apikey}`;
|
|
357
|
+
try {
|
|
358
|
+
const response = await fetch(api);
|
|
359
|
+
if (!response.ok) {
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
const data = await response.json();
|
|
363
|
+
console.log("搜索结果", data);
|
|
364
|
+
const results = data[type] || [];
|
|
365
|
+
return type === "organic_results" ? results.slice(0, 7) : results;
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
console.error("Error searching web:", error);
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
Photoshop: async ({ image_url, instruction }) => {
|
|
373
|
+
// 从aiconfig中查询grok配置
|
|
374
|
+
const aiConfigStr = await redis.get(`ai:list:grok`);
|
|
375
|
+
if (!aiConfigStr) {
|
|
376
|
+
return "未找到AI配置";
|
|
377
|
+
}
|
|
378
|
+
const aiConfig = JSON.parse(aiConfigStr);
|
|
379
|
+
try {
|
|
380
|
+
// 处理单个或多个图片
|
|
381
|
+
const imageUrls = Array.isArray(image_url) ? image_url : [image_url];
|
|
382
|
+
const imgBase64Array = [];
|
|
383
|
+
for (const url of imageUrls) {
|
|
384
|
+
let imgBase64 = "";
|
|
385
|
+
if (url.startsWith("https://imgen.x.ai")) {
|
|
386
|
+
imgBase64 = url;
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
imgBase64 = await uploadImageToR2(url);
|
|
390
|
+
}
|
|
391
|
+
imgBase64Array.push(imgBase64);
|
|
392
|
+
}
|
|
393
|
+
const requestBody = Array.isArray(image_url)
|
|
394
|
+
? {
|
|
395
|
+
model: "grok-imagine-image",
|
|
396
|
+
images: imgBase64Array.map((base64) => ({
|
|
397
|
+
url: base64,
|
|
398
|
+
type: "image_url",
|
|
399
|
+
})),
|
|
400
|
+
prompt: instruction,
|
|
401
|
+
n: 1,
|
|
402
|
+
}
|
|
403
|
+
: {
|
|
404
|
+
model: "grok-imagine-image",
|
|
405
|
+
image: {
|
|
406
|
+
url: imgBase64Array[0],
|
|
407
|
+
type: "image_url",
|
|
408
|
+
},
|
|
409
|
+
prompt: instruction,
|
|
410
|
+
n: 1,
|
|
411
|
+
};
|
|
412
|
+
console.log("最终请求体", requestBody);
|
|
413
|
+
const res = await fetch(aiConfig.host + "/images/edits", {
|
|
414
|
+
method: "POST",
|
|
415
|
+
headers: {
|
|
416
|
+
"Content-Type": "application/json",
|
|
417
|
+
Authorization: `Bearer ${aiConfig.key}`,
|
|
418
|
+
},
|
|
419
|
+
body: JSON.stringify(requestBody),
|
|
420
|
+
});
|
|
421
|
+
if (!res.ok) {
|
|
422
|
+
const err = await res.text();
|
|
423
|
+
console.log(err);
|
|
424
|
+
return err;
|
|
425
|
+
}
|
|
426
|
+
const image = await res.json();
|
|
427
|
+
console.log("画好了", image);
|
|
428
|
+
return image.data;
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
console.error("Error calling AIPS:", error);
|
|
432
|
+
return "调用接口失败" + error;
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
AIVideos: async ({ image_url, instruction }) => {
|
|
436
|
+
// 从aiconfig中查询grok配置
|
|
437
|
+
const aiConfigStr = await redis.get(`ai:list:grok`);
|
|
438
|
+
if (!aiConfigStr) {
|
|
439
|
+
return "未找到AI配置";
|
|
440
|
+
}
|
|
441
|
+
const aiConfig = JSON.parse(aiConfigStr);
|
|
442
|
+
// 处理单个或多个图片
|
|
443
|
+
const imageUrls = Array.isArray(image_url) ? image_url : [image_url];
|
|
444
|
+
const imgBase64Array = [];
|
|
445
|
+
for (const url of imageUrls) {
|
|
446
|
+
let imgBase64 = "";
|
|
447
|
+
if (url) {
|
|
448
|
+
if (url.startsWith("https://imgen.x.ai")) {
|
|
449
|
+
imgBase64 = url;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
imgBase64 = await uploadImageToR2(url);
|
|
453
|
+
}
|
|
454
|
+
imgBase64Array.push(imgBase64);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
const requestBody = imgBase64Array.length === 0
|
|
459
|
+
? {
|
|
460
|
+
model: "grok-imagine-video",
|
|
461
|
+
prompt: instruction,
|
|
462
|
+
aspect_ratio: "16:9",
|
|
463
|
+
resolution: "720p",
|
|
464
|
+
}
|
|
465
|
+
: Array.isArray(image_url)
|
|
466
|
+
? {
|
|
467
|
+
model: "grok-imagine-video",
|
|
468
|
+
images: imgBase64Array.map((base64) => ({
|
|
469
|
+
url: base64,
|
|
470
|
+
type: "image_url",
|
|
471
|
+
})),
|
|
472
|
+
prompt: instruction,
|
|
473
|
+
resolution: "720p",
|
|
474
|
+
}
|
|
475
|
+
: {
|
|
476
|
+
model: "grok-imagine-video",
|
|
477
|
+
image: {
|
|
478
|
+
url: imgBase64Array[0],
|
|
479
|
+
type: "image_url",
|
|
480
|
+
},
|
|
481
|
+
prompt: instruction,
|
|
482
|
+
resolution: "720p",
|
|
483
|
+
};
|
|
484
|
+
console.log("最终请求体", requestBody);
|
|
485
|
+
const res = await fetch(aiConfig.host + "/videos/generations", {
|
|
486
|
+
method: "POST",
|
|
487
|
+
headers: {
|
|
488
|
+
"Content-Type": "application/json",
|
|
489
|
+
Authorization: `Bearer ${aiConfig.key}`,
|
|
490
|
+
},
|
|
491
|
+
body: JSON.stringify(requestBody),
|
|
492
|
+
});
|
|
493
|
+
if (!res.ok) {
|
|
494
|
+
const err = await res.text();
|
|
495
|
+
console.log(err);
|
|
496
|
+
return err;
|
|
497
|
+
}
|
|
498
|
+
const video = await res.json();
|
|
499
|
+
console.log("画好了", video);
|
|
500
|
+
return {
|
|
501
|
+
data: video,
|
|
502
|
+
msg: "生成任务已提交,稍后会自动发送生成结果,无需继续等待~,将request_id提供给用户,方便查询生成状态",
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
console.error("Error calling AIPS:", error);
|
|
507
|
+
return "调用接口失败" + error;
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
AIVideoResult: async ({ request_id }) => {
|
|
511
|
+
// 从aiconfig中查询grok配置
|
|
512
|
+
const aiConfigStr = await redis.get(`ai:list:grok`);
|
|
513
|
+
if (!aiConfigStr) {
|
|
514
|
+
return "未找到AI配置";
|
|
515
|
+
}
|
|
516
|
+
const aiConfig = JSON.parse(aiConfigStr);
|
|
517
|
+
try {
|
|
518
|
+
const res = await fetch(aiConfig.host + `/videos/${request_id}`, {
|
|
519
|
+
method: "GET",
|
|
520
|
+
headers: {
|
|
521
|
+
"Content-Type": "application/json",
|
|
522
|
+
Authorization: `Bearer ${aiConfig.key}`,
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
if (!res.ok) {
|
|
526
|
+
const err = await res.text();
|
|
527
|
+
console.log(err);
|
|
528
|
+
return err;
|
|
529
|
+
}
|
|
530
|
+
const video = await res.json();
|
|
531
|
+
console.log("视频生成结果", video);
|
|
532
|
+
return video;
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
console.error("Error calling AIVideoResult:", error);
|
|
536
|
+
return "调用接口失败" + error;
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
/**
|
|
540
|
+
* 获取TTS模型列表
|
|
541
|
+
* @returns TTS模型列表
|
|
542
|
+
*/
|
|
543
|
+
GetTTSModels: async () => {
|
|
544
|
+
const ttsClient = new TTSClient();
|
|
545
|
+
const models = await ttsClient.getModels("v4");
|
|
546
|
+
return models;
|
|
547
|
+
},
|
|
548
|
+
/**
|
|
549
|
+
* 获取 ollama 模型列表
|
|
550
|
+
* @returns ollama 模型列表
|
|
551
|
+
*/
|
|
552
|
+
OllamaListModels: async () => {
|
|
553
|
+
const res = await fetch("http://localhost:11434/v1/models");
|
|
554
|
+
if (!res.ok) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
const data = await res.json();
|
|
558
|
+
return data.data;
|
|
559
|
+
},
|
|
560
|
+
/**
|
|
561
|
+
* 安装 ollama 模型
|
|
562
|
+
* @param modelName 模型名称
|
|
563
|
+
* @returns 安装结果
|
|
564
|
+
*/
|
|
565
|
+
OllamaInstallModel: async (modelName) => {
|
|
566
|
+
try {
|
|
567
|
+
const res = await fetch("http://localhost:11434/v1/models", {
|
|
568
|
+
method: "POST",
|
|
569
|
+
headers: {
|
|
570
|
+
"Content-Type": "application/json",
|
|
571
|
+
},
|
|
572
|
+
body: JSON.stringify({
|
|
573
|
+
model: modelName,
|
|
574
|
+
}),
|
|
575
|
+
});
|
|
576
|
+
if (!res.ok) {
|
|
577
|
+
const err = await res.text();
|
|
578
|
+
console.log(err);
|
|
579
|
+
return { success: false, error: err };
|
|
580
|
+
}
|
|
581
|
+
const data = await res.json();
|
|
582
|
+
console.log("安装结果", data);
|
|
583
|
+
return { success: true };
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
console.error("Error installing Ollama model:", error);
|
|
587
|
+
return { success: false, error: error };
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
/**
|
|
591
|
+
* 运行cmd命令
|
|
592
|
+
* @param command 命令字符串
|
|
593
|
+
* @returns 命令执行结果
|
|
594
|
+
*/
|
|
595
|
+
RunCommand: async ({ command }) => {
|
|
596
|
+
const { exec } = await import('child_process');
|
|
597
|
+
return new Promise((resolve) => {
|
|
598
|
+
exec(command, (error, stdout, stderr) => {
|
|
599
|
+
if (error) {
|
|
600
|
+
console.error(`Error executing command: ${error}`);
|
|
601
|
+
resolve(`Error: ${error.message}`);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (stderr) {
|
|
605
|
+
console.error(`Command stderr: ${stderr}`);
|
|
606
|
+
resolve(`Error: ${stderr}`);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
console.log(`Command stdout: ${stdout}`);
|
|
610
|
+
resolve(stdout);
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
},
|
|
614
|
+
getSKILLDetail: async ({ skillName }) => {
|
|
615
|
+
const skill = loadSkillDetail(skillName);
|
|
616
|
+
return skill;
|
|
617
|
+
},
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
export { availableTools, tools };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getConfigValue } from 'alemonjs';
|
|
2
|
+
|
|
3
|
+
getConfigValue().aiChat || {};
|
|
4
|
+
const getTimeString = () => {
|
|
5
|
+
const now = new Date();
|
|
6
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
7
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
8
|
+
const hours = String(now.getHours()).padStart(2, "0");
|
|
9
|
+
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
10
|
+
return `${month}-${day} ${hours}:${minutes}`;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { getTimeString };
|