返回 AiToEarn
ai.service.ts
根目录 / project / aitoearn-electron / server / src / modules / tools / ai.service.ts
1 /*
2 * @Author: nevin
3 * @Date: 2024-06-17 19:19:15
4 * @LastEditTime: 2024-09-05 15:19:25
5 * @LastEditors: nevin
6 * @Description:
7 */
8 import { Injectable } from '@nestjs/common';
9 import axios from 'axios';
10 import OpenAI from 'openai';
11 import { Observable } from 'rxjs';
12 import { RedisService } from 'src/lib/redis/redis.service';
13 import { v4 as uuidv4 } from 'uuid';
14
15 export enum FireflycardTempTypes {
16 A = 'tempA', // 默认
17 B = 'tempB', // 书摘
18 C = 'tempC', // 透明
19 Jin = 'tempJin', // 金句
20 Memo = 'tempMemo', // 备忘录
21 Easy = 'tempEasy', // 便当
22 BlackSun = 'tempBlackSun', // 黑日
23 E = 'tempE', // 框界
24 Write = 'tempWrite', // 手写
25 Code = 'code', // 代码
26 D = 'tempD', // 图片(暂时不用)
27 }
28 @Injectable()
29 export class AiToolsService {
30 openai: OpenAI;
31
32 constructor(private readonly redisService: RedisService) {
33 this.openai = new OpenAI({
34 apiKey: process.env.QWEN_KEY,
35 baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
36 });
37 }
38
39 /**
40 * 视频智能标题
41 * @param url
42 * @param min
43 * @param max
44 * @returns
45 */
46 async videoAiTitle(url: string, min?: 5, max?: 50) {
47 const completion = await this.openai.chat.completions.create({
48 model: 'qwen-omni-turbo', //模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
49 stream: true,
50 messages: [
51 {
52 role: 'system',
53 content: [
54 {
55 type: 'text',
56 text: '你是一个短视频创作者,请帮我作品进行智能标题设置,只需要返回标题',
57 },
58 ],
59 },
60 {
61 role: 'user',
62 content: [
63 {
64 type: 'video_url',
65 video_url: {
66 url: url,
67 },
68 },
69 {
70 type: 'text',
71 text: `给视频设置一个标题,长度为${min}到${max}个字. 只需要返回该标题`,
72 },
73 ] as any,
74 },
75 ],
76 stream_options: {
77 include_usage: true,
78 },
79 modalities: ['text'],
80 });
81 let res = '';
82
83 for await (const chunk of completion) {
84 if (Array.isArray(chunk.choices) && chunk.choices.length > 0) {
85 const content = chunk.choices[0].delta.content;
86 if (content === null) return res;
87 res += content;
88 } else {
89 console.log(chunk.usage);
90 }
91 }
92
93 return res;
94 }
95
96 /**
97 * 智能评论图片
98 * @param imgUrl
99 * @param title
100 * @param desc
101 * @param max
102 * @returns
103 */
104 async reviewImgByAi(
105 imgUrl: string,
106 title: string = '无',
107 desc: string = '无',
108 max = 50,
109 ): Promise<string> {
110 const completion = await this.openai.chat.completions.create({
111 stream: true,
112 model: 'qwen-omni-turbo',
113 messages: [
114 {
115 role: 'system',
116 content: `你是我的好友,请对我发的短视频的作品或者朋友圈短视频作品进行评论,我会提供作品的封面图. 请用中文回复,并且回复内容不超过${max}字.只需要返回评论内容.`,
117 },
118 {
119 role: 'user',
120 content: [
121 {
122 type: 'image_url',
123 image_url: {
124 url: imgUrl,
125 },
126 },
127 {
128 type: 'text',
129 text: `作品标题: ${title}, 作品描述: ${desc}`,
130 },
131 ],
132 },
133 ],
134 stream_options: {
135 include_usage: true,
136 },
137 modalities: ['text'],
138 });
139
140 let res = '';
141
142 for await (const chunk of completion) {
143 if (Array.isArray(chunk.choices) && chunk.choices.length > 0) {
144 console.log(chunk.choices[0].delta);
145 const content = chunk.choices[0].delta.content;
146 if (content === null) return res;
147 res += content;
148 } else {
149 console.log(chunk.usage);
150 }
151 }
152
153 return res;
154 }
155
156 // 智能评论
157 async reviewAi(
158 title: string,
159 desc: string = '无',
160 max = 50,
161 ): Promise<string> {
162 const completion = await this.openai.chat.completions.create({
163 stream: true,
164 model: 'qwen-omni-turbo',
165 messages: [
166 {
167 role: 'system',
168 content: `你是我的好友,请对我发的短视频作品或者朋友圈作品进行评论. 请用中文回复,并且回复内容不超过${max}字.只需要返回评论内容.`,
169 },
170 {
171 role: 'user',
172 content: `作品标题: ${title}, 作品描述: ${desc}`,
173 },
174 ],
175 stream_options: {
176 include_usage: true,
177 },
178 modalities: ['text'],
179 });
180
181 let res = '';
182
183 for await (const chunk of completion) {
184 if (Array.isArray(chunk.choices) && chunk.choices.length > 0) {
185 console.log(chunk.choices[0].delta);
186 const content = chunk.choices[0].delta.content;
187 if (content === null) return res;
188 res += content;
189 } else {
190 console.log(chunk.usage);
191 }
192 }
193
194 return res;
195 }
196
197 /**
198 * 智能回复评论
199 * @param content
200 * @param title
201 * @param desc
202 * @param max
203 */
204 async reviewAiRecover(
205 content: string,
206 title: string = '无',
207 desc: string = '无',
208 max = 50,
209 ): Promise<string> {
210 const completion = await this.openai.chat.completions.create({
211 stream: true,
212 model: 'qwen-omni-turbo',
213 messages: [
214 {
215 role: 'system',
216 content: `你是一个风趣幽默又有分寸的文字创作者的助手,请帮我对别人对我作品的评论进行回复. 请用中文回复,并且回复内容不超过${max}字.只需要返回你的回复内容.`,
217 },
218 {
219 role: 'user',
220 content: `作品标题: ${title}, 作品描述: ${desc}, 评论内容: ${content}`,
221 },
222 ],
223 stream_options: {
224 include_usage: true,
225 },
226 modalities: ['text'],
227 });
228
229 let res = '';
230
231 for await (const chunk of completion) {
232 if (Array.isArray(chunk.choices) && chunk.choices.length > 0) {
233 console.log(chunk.choices[0].delta);
234 const content = chunk.choices[0].delta.content;
235 if (content === null) return res;
236 res += content;
237 } else {
238 console.log(chunk.usage);
239 }
240 }
241
242 return res;
243 }
244
245 /**
246 * 生成AI的html图文
247 * @param content
248 */
249 async aiArticleHtml(content: string): Promise<string> {
250 const completion = await this.openai.chat.completions.create({
251 stream: true,
252 model: 'deepseek-v3',
253 messages: [
254 {
255 role: 'system',
256 content: `你是一个web前端开发者,根据我发的内容进行web页面开发.注意,只需要返回代码`,
257 },
258 {
259 role: 'user',
260 content,
261 },
262 ],
263 stream_options: {
264 include_usage: true,
265 },
266 modalities: ['text'],
267 });
268
269 let res = '';
270
271 for await (const chunk of completion) {
272 if (Array.isArray(chunk.choices) && chunk.choices.length > 0) {
273 console.log(chunk.choices[0].delta);
274 const content = chunk.choices[0].delta.content;
275 if (content === null) return res;
276 res += content;
277 } else {
278 console.log(chunk.usage);
279 }
280 }
281
282 return res;
283 }
284
285 /**
286 * 生成AI的html图文-测试
287 * @param content
288 */
289 async aiArticleHtml2(
290 model: string,
291 content: string,
292 content2: string,
293 ): Promise<string> {
294 const completion = await this.openai.chat.completions.create({
295 stream: true,
296 model,
297 messages: [
298 {
299 role: 'system',
300 content: content2,
301 },
302 {
303 role: 'user',
304 content,
305 },
306 ],
307 stream_options: {
308 include_usage: true,
309 },
310 modalities: ['text'],
311 });
312
313 let res = '';
314
315 for await (const chunk of completion) {
316 if (Array.isArray(chunk.choices) && chunk.choices.length > 0) {
317 console.log(chunk.choices[0].delta);
318 const content = chunk.choices[0].delta.content;
319 if (content === null) return res;
320 res += content;
321 } else {
322 console.log(chunk.usage);
323 }
324 }
325
326 return res;
327 }
328
329 /**
330 * 获取AI的markdown
331 * @param content
332 */
333 async aiMarkdown(data: { content: string; prompt: string }): Promise<string> {
334 const taskId = uuidv4().replace(/-/g, '');
335 this.openai.chat.completions
336 .create({
337 stream: true,
338 model: 'deepseek-v3',
339 messages: [
340 {
341 role: 'system',
342 content: `将以下内容美化成Markdown格式,${data.prompt},只需要美化样式,内容不改变,只返回markdown格式,不返回其他内容`,
343 },
344 {
345 role: 'user',
346 content: data.content,
347 },
348 ],
349 stream_options: {
350 include_usage: true,
351 },
352 modalities: ['text'],
353 })
354 .then(async (completion) => {
355 let text = '';
356
357 for await (const chunk of completion) {
358 if (Array.isArray(chunk.choices) && chunk.choices.length > 0) {
359 console.log(chunk.choices[0].delta);
360 const content = chunk.choices[0].delta.content;
361 if (content !== null) text += content;
362 } else {
363 console.log(chunk.usage);
364 }
365 }
366 this.redisService.setKey(`aiMarkdown:${taskId}`, text, 60 * 5);
367 });
368
369 return taskId;
370 }
371
372 // 获取aiMarkdown的结果
373 async getAiMarkdown(taskId: string) {
374 try {
375 const text = await this.redisService.get(`aiMarkdown:${taskId}`, false);
376 return text;
377 } catch (error) {
378 console.log('------ getAiMarkdown error ----', error);
379
380 return '';
381 }
382 }
383
384 /**
385 * 生成AI的html图文
386 * @param content
387 */
388 async aiArticleHtmlSse(content: string): Promise<Observable<any>> {
389 const completion = await this.openai.chat.completions.create({
390 stream: true,
391 model: 'deepseek-v3',
392 messages: [
393 {
394 role: 'system',
395 content: `你是一个web前端开发者,根据我发的内容进行web页面开发.注意,只需要返回代码`,
396 },
397 {
398 role: 'user',
399 content,
400 },
401 ],
402 stream_options: {
403 include_usage: true,
404 },
405 modalities: ['text'],
406 });
407
408 return new Observable((observer) => {
409 (async () => {
410 for await (const chunk of completion) {
411 if (Array.isArray(chunk.choices) && chunk.choices.length > 0) {
412 console.log(chunk.choices[0].delta);
413 const content = chunk.choices[0].delta.content;
414 if (content === null) return observer.complete();
415 observer.next(content);
416 } else {
417 console.log(chunk.usage);
418 }
419 }
420 observer.complete();
421 })();
422 });
423 }
424
425 /**
426 * 获取流光卡片内容
427 * @param title
428 * @param content
429 * @returns Promise<string> 返回流光卡片图片编码
430 */
431 async fireflycard(
432 content: string,
433 temp: FireflycardTempTypes,
434 title: string = '',
435 ): Promise<Buffer> {
436 const url = `https://fireflycard-api.302ai.cn/api/saveImg`;
437
438 const body = {
439 form: {
440 title,
441 content,
442 pagination: '01',
443 },
444 style: {
445 align: 'left',
446 backgroundName: 'vertical-blue-color-29',
447 backShadow: '',
448 font: 'Alibaba-PuHuiTi-Regular',
449 width: 440,
450 ratio: '',
451 height: 0,
452 fontScale: 1,
453 padding: '30px',
454 borderRadius: '15px',
455 color: '#000000',
456 opacity: 1,
457 blur: 0,
458 backgroundAngle: '0deg',
459 lineHeights: {
460 content: '',
461 },
462 letterSpacings: {
463 content: '',
464 },
465 },
466 switchConfig: {
467 showIcon: false,
468 showDate: true,
469 showTitle: !!title,
470 showContent: true,
471 showAuthor: false,
472 showTextCount: false,
473 showQRCode: false,
474 showPageNum: false,
475 showWatermark: false,
476 },
477 temp,
478 language: 'zh',
479 };
480
481 const response = await axios.post(url, body, {
482 responseType: 'arraybuffer',
483 });
484 return response.data;
485 }
486
487 /**
488 * 即梦生成封面 任务提交
489 * @returns
490 */
491 async upJmImgTask(data: {
492 prompt: string;
493 width: number;
494 height: number;
495 sessionIds: string[];
496 }) {
497 const body = {
498 id: uuidv4(), // 标识符
499 // jimeng-3.0(默认) / jimeng-2.1 / jimeng-2.0-pro / jimeng-2.0 / jimeng-1.4 / jimeng-xl-pro
500 model: 'jimeng-3.0',
501 // 提示词,必填
502 prompt: data.prompt,
503 // 反向提示词,默认空字符串
504 negativePrompt: '',
505 // 图像宽度,默认1024
506 width: data.width,
507 // 图像高度,默认1024
508 height: data.height,
509 // 精细度,取值范围0-1,默认0.5
510 sample_strength: 0.5,
511 sessionId: data.sessionIds.join(','),
512 };
513
514 const result = await axios.post<{
515 code: number; // 0;
516 msg: string; // 'success';
517 data: {
518 _id: string; // 'f05f1c88b28ced57d42c29b799185079';
519 taskId: string; // 'f05f1c88b28ced57d42c29b799185079';
520 id: string; // '0130c639-3d80-4e77-8da3-64de1dba8d2e';
521 model: string; // 'jimeng-3.0';
522 prompt: string; // '美好花园';
523 negativePrompt: string; // '';
524 width: number; // 10;
525 height: number; // 10;
526 sample_strength: number; // 0.5;
527 status: string; // 'pending';
528 createTime: string; // '2025-05-14 22:31:32';
529 images: string[]; // [];
530 };
531 }>('https://att-contents.yikart.cn/api/third/v1/images/generations', body);
532
533 if (!!result.data.code) {
534 console.log('------- upJmImgTask error ---', result.data.msg);
535 return '';
536 }
537
538 return result.data.data.taskId;
539 }
540
541 /**
542 * 即梦生成图片任务获取
543 * @returns
544 */
545 async getJmImgTaskRes(taskId: string): Promise<{
546 taskId: string;
547 status: string;
548 imgList: string[];
549 }> {
550 const result = await axios.get<{
551 code: number; // 0;
552 msg: string; // 'success';
553 data: {
554 status: string;
555 taskId: string; // '997272a47ac2b46e2b7660ae905b33cb';
556 images: {
557 url: string;
558 }[];
559 };
560 }>('https://att-contents.yikart.cn/api/third/v1/images/query', {
561 params: { taskId },
562 });
563
564 console.log(
565 '----------- getJmImgTaskRes result.data ----------',
566 result.data,
567 );
568
569 if (!!result.data.code) {
570 console.log('------- getJmImgTaskRes error ---', result.data.msg);
571 return {
572 taskId: '',
573 status: 'error',
574 imgList: [],
575 };
576 }
577
578 if (result.data.data.status === 'failed') {
579 return {
580 taskId,
581 status: 'failed',
582 imgList: [],
583 };
584 }
585
586 if (result.data.data.status !== 'success') {
587 return {
588 taskId,
589 status: result.data.data.status,
590 imgList: [],
591 };
592 }
593
594 return {
595 taskId,
596 status: 'success',
597 imgList: result.data.data.images.map((item: any) => {
598 return item.url;
599 }),
600 };
601 }
602 }
603
603 lines TYPESCRIPT