yuanjiajun commited on
Commit
139dfb9
·
1 Parent(s): e2ed47d

feat: 单图

Browse files
src/const/article.ts CHANGED
@@ -458,3 +458,106 @@ export const emotionalStoryEnding = [
458
  '结局探讨了自由的含义,主角为了追求自由付出了巨大的代价',
459
  '结局是一个开放式的,让读者自己想象后续发展',
460
  ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  '结局探讨了自由的含义,主角为了追求自由付出了巨大的代价',
459
  '结局是一个开放式的,让读者自己想象后续发展',
460
  ];
461
+
462
+ export const emotionalStoryCover = [
463
+ '乡村土路',
464
+ '山间小溪',
465
+ '麦田间的田埂',
466
+ '村头的老槐树',
467
+ '柴火垛旁的空地',
468
+ '农家小院的篱笆墙',
469
+ '黄昏下的打谷场',
470
+ '清晨的露珠挂在草叶上',
471
+ '河边的垂柳',
472
+ '雨后的乡间小道',
473
+ '田边的灌溉渠',
474
+ '村口的石磨',
475
+ '破旧的茅草屋',
476
+ '山脚下的梯田',
477
+ '堆满干草的草棚',
478
+ '菜园里的蔬菜架',
479
+ '田野里的稻草人',
480
+ '夏季的瓜棚',
481
+ '冬季的雪压树枝',
482
+ '秋天的落叶满地',
483
+ '村后的小山坡',
484
+ '河边的洗衣石',
485
+ '田边的野花丛',
486
+ '村口的老井',
487
+ '屋顶的烟囱',
488
+ '被雨水冲刷的土墙',
489
+ '麦收后的麦茬地',
490
+ '稻穗低垂的稻田',
491
+ '菜园里的水井',
492
+ '村中的石板路',
493
+ '院门口的石狮子',
494
+ '老房子的木窗',
495
+ '黄昏的炊烟',
496
+ '夏日的蝉鸣树',
497
+ '秋天的柿子树',
498
+ '冬日的冰挂树枝',
499
+ '春天的桃花林',
500
+ '夏日的荷花塘(边缘部分,不拍荷花)',
501
+ '雨后的水洼',
502
+ '村边的小树林',
503
+ '田边的灌溉水车',
504
+ '废弃的石碾',
505
+ '堆满农具的角落',
506
+ '乡村小学的操场',
507
+ '村里的祠堂',
508
+ '晒谷场上的竹席',
509
+ '被藤蔓缠绕的电线杆',
510
+ '田边的稻草人',
511
+ '夏日的丝瓜架',
512
+ '冬日的干草堆',
513
+ '秋天的葡萄园',
514
+ '夏日的向日葵地',
515
+ '河边的菖蒲',
516
+ '田边的艾草',
517
+ '村中的老戏台',
518
+ '夏日的葡萄藤架',
519
+ '秋日的玉米地',
520
+ '冬天的枯树林',
521
+ '春天的油菜花田',
522
+ '夏日的竹林',
523
+ '河边的芦苇荡',
524
+ '田边的荆棘丛',
525
+ '村中的石拱桥',
526
+ '夏日的稻田蛙鸣处',
527
+ '秋日的山楂树',
528
+ '冬日的雪地脚印(如果脚印不太明显可算)',
529
+ '春天的榆钱树',
530
+ '村中的老柳树下',
531
+ '田边的排水沟',
532
+ '夏日的豆角架',
533
+ '秋日的南瓜地',
534
+ '冬日的树挂雾凇',
535
+ '春天的杏花林',
536
+ '村中的晒谷场角落',
537
+ '田边的简易瓜棚',
538
+ '夏日的薄荷地',
539
+ '秋日的板栗树',
540
+ '冬日的冻土裂缝',
541
+ '春天的杨树林',
542
+ '河边的苔藓',
543
+ '田边的废弃犁具',
544
+ '村中的古井台',
545
+ '夏日的稻田飞虫',
546
+ '秋日的酸枣树',
547
+ '冬日的秃树鸟巢',
548
+ '春天的果园小径',
549
+ '村中的柴火堆',
550
+ '田边的灌溉水阀',
551
+ '夏日的薄荷丛',
552
+ '秋日的核桃树',
553
+ '冬日的篱笆上的冰柱',
554
+ '春天的蒲公英地',
555
+ '村中的老榆树下',
556
+ '田边的水洼倒影',
557
+ '夏日的韭菜地',
558
+ '秋日的柿子树红果',
559
+ '冬日的雪地枯草',
560
+ '春天的柳树新芽',
561
+ '村中的石墩',
562
+ '田边的野花小径',
563
+ ];
src/service/article-service.ts CHANGED
@@ -1,39 +1,24 @@
1
- import { bufferToBase64ImageSrc, delay, getFluxImageBuffer, getRandomUniqueElements, retryAsync } from '@/utils';
2
 
3
- import { requestQw, uploadFile } from '@/utils';
4
- import { emotionalStoryTopic, emotionalStoryStyle, emotionalStoryEnding } from '@/const';
5
  const htmlToDocx = require('html-to-docx');
6
 
7
- async function getImageBase64ByText(text: string, hfApiKey: string) {
8
- console.log(`------------ 开始获取图片,原文案:${text} ---------------`);
9
-
10
- const summary = await requestQw({
11
- messages: [
12
- {
13
- role: 'user',
14
- content: `翻译成英语并简化内容提炼核心,控制在十五个单词内:${text}`,
15
- },
16
- ],
17
- model: 'qwen-plus',
18
- });
19
- console.log(`summary:${summary}`);
20
- const buffer = await getFluxImageBuffer(`modern China:${summary}`, hfApiKey);
21
- const imageBase64 = bufferToBase64ImageSrc(buffer, 'image/png');
22
- console.log('------------- 图片Base64获取成功 ---------------');
23
- return imageBase64;
24
- }
25
-
26
- export const processArticleServe = async (data: { title: string; content: string; config: { output: string; hfApiKey: string } }) => {
27
  const { title, content, config } = data;
28
-
29
  // 将富文本内容分割成段落数组
30
  const paragraphs = content.split(/\\n|\n/).filter((p) => p.trim() !== '');
31
- const imageParagraphCount = Math.max(3, Math.ceil(paragraphs.length / (3 * 2)));
32
 
33
  // 遍历段落,根据段落内容调用接口生成图片并插入到HTML中
34
  const htmlArray = [`<h1>${title}</h1>`];
35
  let promiseArray = [];
36
- let imageCount = 0;
37
 
38
  for (let i = 0; i < paragraphs.length; i++) {
39
  const isGeneImage = i === 0 || (i % imageParagraphCount === 0 && i <= paragraphs.length - (imageParagraphCount - 1));
@@ -41,29 +26,24 @@ export const processArticleServe = async (data: { title: string; content: string
41
 
42
  promiseArray.push(
43
  new Promise(async (resolve) => {
44
- try {
45
- // 如果是文章开头(i === 0)或者符合每两或三个段落插入图片的规则(且不在最后两段内)
46
- if (isGeneImage) {
47
- imageCount++;
48
-
49
- const text = i ? paragraphs[i - 3] + paragraphs[i - 2] + paragraphs[i - 1] : title;
50
- const base64ImageUri = await retryAsync(() => getImageBase64ByText(text, config.hfApiKey), 1, 5 * 1000);
51
- // 创建 Base64 数据 URI
52
- htmlArray[i + 1] += `<img src="${base64ImageUri}" alt="Generated Image"><br>`;
53
- }
54
- } catch (error: any) {
55
- console.error('Error calling API to generate image:', error);
56
  }
57
 
58
  htmlArray[i + 1] += `<p>${paragraphs[i]}</p>`;
59
-
60
  resolve(1);
61
  }),
62
  );
63
 
64
  if (isGeneImage) {
65
- imageCount++;
66
- if (imageCount % 3 === 0) {
67
  const start = Date.now();
68
  await Promise.all(promiseArray);
69
  await delay(Math.max(65 * 1000 - (Date.now() - start), 0));
@@ -76,13 +56,13 @@ export const processArticleServe = async (data: { title: string; content: string
76
 
77
  const htmlWithImages = htmlArray.join('');
78
 
79
- if (config.output === 'html') {
80
  return {
81
  article: htmlWithImages,
82
  };
83
  }
84
 
85
- if (config.output === 'docx') {
86
  const outputFilename = `docx-${Date.now()}.docx`;
87
  const docxBuffer = await htmlToDocx(htmlWithImages, {
88
  orientation: 'portrait',
@@ -104,7 +84,7 @@ export const processArticleElementServe = async () => {
104
  const topic = getRandomUniqueElements(emotionalStoryTopic, 1)[0];
105
  const styleList = getRandomUniqueElements(emotionalStoryStyle, 5);
106
  const style = styleList.join(',');
107
- const ending = getRandomUniqueElements(emotionalStoryEnding, 1)[0];;
108
 
109
  return {
110
  topic,
 
1
+ import { delay, getImageBase64ByText, getRandomUniqueElements, retryAsync } from '@/utils';
2
 
3
+ import { uploadFile } from '@/utils';
4
+ import { emotionalStoryTopic, emotionalStoryStyle, emotionalStoryEnding, emotionalStoryCover } from '@/const';
5
  const htmlToDocx = require('html-to-docx');
6
 
7
+ export const processArticleServe = async (data: {
8
+ title: string;
9
+ content: string;
10
+ config: { output: string; hfApiKey: string; imageCount: number };
11
+ }) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  const { title, content, config } = data;
13
+ const { output = 'docx', hfApiKey, imageCount = 6 } = config;
14
  // 将富文本内容分割成段落数组
15
  const paragraphs = content.split(/\\n|\n/).filter((p) => p.trim() !== '');
16
+ const imageParagraphCount = Math.ceil(paragraphs.length / imageCount);
17
 
18
  // 遍历段落,根据段落内容调用接口生成图片并插入到HTML中
19
  const htmlArray = [`<h1>${title}</h1>`];
20
  let promiseArray = [];
21
+ let currentImageCount = 0;
22
 
23
  for (let i = 0; i < paragraphs.length; i++) {
24
  const isGeneImage = i === 0 || (i % imageParagraphCount === 0 && i <= paragraphs.length - (imageParagraphCount - 1));
 
26
 
27
  promiseArray.push(
28
  new Promise(async (resolve) => {
29
+ // 如果是文章开头(i === 0)或者符合每两或三个段落插入图片的规则(且不在最后两段内)
30
+ if (isGeneImage) {
31
+ currentImageCount++;
32
+
33
+ const text = i ? paragraphs[i - 3] + paragraphs[i - 2] + paragraphs[i - 1] : title;
34
+ const base64ImageUri = await retryAsync(() => getImageBase64ByText(text, hfApiKey), 1, 5 * 1000);
35
+ // 创建 Base64 数据 URI
36
+ htmlArray[i + 1] += `<img src="${base64ImageUri}" alt="Generated Image"><br>`;
 
 
 
 
37
  }
38
 
39
  htmlArray[i + 1] += `<p>${paragraphs[i]}</p>`;
 
40
  resolve(1);
41
  }),
42
  );
43
 
44
  if (isGeneImage) {
45
+ currentImageCount++;
46
+ if (currentImageCount % 3 === 0) {
47
  const start = Date.now();
48
  await Promise.all(promiseArray);
49
  await delay(Math.max(65 * 1000 - (Date.now() - start), 0));
 
56
 
57
  const htmlWithImages = htmlArray.join('');
58
 
59
+ if (output === 'html') {
60
  return {
61
  article: htmlWithImages,
62
  };
63
  }
64
 
65
+ if (output === 'docx') {
66
  const outputFilename = `docx-${Date.now()}.docx`;
67
  const docxBuffer = await htmlToDocx(htmlWithImages, {
68
  orientation: 'portrait',
 
84
  const topic = getRandomUniqueElements(emotionalStoryTopic, 1)[0];
85
  const styleList = getRandomUniqueElements(emotionalStoryStyle, 5);
86
  const style = styleList.join(',');
87
+ const ending = getRandomUniqueElements(emotionalStoryEnding, 1)[0];
88
 
89
  return {
90
  topic,
src/utils/common.ts CHANGED
@@ -1,4 +1,3 @@
1
- import axios from 'axios';
2
  import util from 'util';
3
  const setTimeoutPromise = util.promisify(setTimeout);
4
 
@@ -21,23 +20,6 @@ export async function retryAsync(func: () => Promise<any>, x = 3, timeout = 10 *
21
  throw new Error(`Async function failed after ${x} attempts.`);
22
  }
23
 
24
- export async function requestQw(data: any) {
25
- const qwToken = 'sBy1ogROHqapzX0CcdoyjCl$7wAX1NzRpOPRUMrQGgN8J7jSxmMQWreOgkeheTFbylzpf8Gz1g_n0';
26
-
27
- try {
28
- const response = await axios.post('https://Joey7938-joe-qw-api.hf.space/api/chat/completions', data, {
29
- headers: {
30
- 'Content-Type': 'application/json',
31
- Authorization: `Bearer ${qwToken}`,
32
- },
33
- });
34
-
35
- return response.data.choices[0].message.content;
36
- } catch (error: any) {
37
- throw new Error(error.response.data);
38
- }
39
- }
40
-
41
  export function getRandomUniqueElements(arr: any[], x: number): any[] {
42
  const result: any[] = [];
43
  const copyArr = arr.slice();
 
 
1
  import util from 'util';
2
  const setTimeoutPromise = util.promisify(setTimeout);
3
 
 
20
  throw new Error(`Async function failed after ${x} attempts.`);
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  export function getRandomUniqueElements(arr: any[], x: number): any[] {
24
  const result: any[] = [];
25
  const copyArr = arr.slice();
src/utils/file-utils.ts CHANGED
@@ -3,6 +3,9 @@ import fs from 'fs';
3
  import os from 'os';
4
  import path from 'path';
5
  import sharp from 'sharp';
 
 
 
6
 
7
  // 创建上传文件夹,如果不存在
8
  export const createUploadDir = (): string => {
@@ -100,3 +103,30 @@ export async function blobToArrayBuffer(blobOrArrayBuffer: Blob | ArrayBuffer){
100
  }
101
  }
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import os from 'os';
4
  import path from 'path';
5
  import sharp from 'sharp';
6
+ import { getRandomUniqueElements } from './common';
7
+ import { requestQwImage } from './hugging-face';
8
+ import { emotionalStoryCover } from '@/const';
9
 
10
  // 创建上传文件夹,如果不存在
11
  export const createUploadDir = (): string => {
 
103
  }
104
  }
105
 
106
+
107
+ export async function getImageBase64ByText(text: string, hfApiKey: string) {
108
+ console.log(`------------ 开始获取图片,原文案:${text} ---------------`);
109
+
110
+ // const summary = await requestQw({
111
+ // messages: [
112
+ // {
113
+ // role: 'user',
114
+ // content: `翻译成英语并简化内容提炼核心,控制在十五个单词内:${text}`,
115
+ // },
116
+ // ],
117
+ // model: 'qwen-plus',
118
+ // });
119
+ // console.log(`summary:${summary}`);
120
+ // const buffer = await getFluxImageBuffer(`modern China:${summary}`, hfApiKey);
121
+ // const imageBase64 = bufferToBase64ImageSrc(buffer, 'image/png');
122
+
123
+ const coverTheme = getRandomUniqueElements(emotionalStoryCover, 1)[0];
124
+ const base64 = await requestQwImage({
125
+ prompt: `手机拍摄,贴近现实生活,灰土气息,纯风景,不要人物和动物:${coverTheme}`,
126
+ model: 'qwen-plus',
127
+ response_format: 'b64_json',
128
+ });
129
+ const imageBase64 = `data:image/png;base64,${base64}`;
130
+ console.log('------------- 图片Base64获取成功 ---------------');
131
+ return imageBase64;
132
+ }
src/utils/hugging-face.ts CHANGED
@@ -14,7 +14,7 @@ export async function getTranslatedText(text: string, apiKey: string) {
14
  },
15
  );
16
  const translatedText = translatedData.data[0].translation_text as string;
17
-
18
  return translatedText;
19
  } catch (error) {
20
  throw new Error(`翻译失败,错误信息:${error}`);
@@ -34,15 +34,32 @@ export async function getSummaryText(text: string, apiKey: string) {
34
  },
35
  },
36
  );
37
-
38
  const summaryText = summaryData.data[0].summary_text as string;
39
-
40
  return summaryText;
41
  } catch (error) {
42
  throw new Error(`总结失败,错误信息:${error}`);
43
  }
44
  }
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  export async function requestQwImage(data: any) {
47
  const qwToken = 'sBy1ogROHqapzX0CcdoyjCl$7wAX1NzRpOPRUMrQGgN8J7jSxmMQWreOgkeheTFbylzpf8Gz1g_n0';
48
 
 
14
  },
15
  );
16
  const translatedText = translatedData.data[0].translation_text as string;
17
+
18
  return translatedText;
19
  } catch (error) {
20
  throw new Error(`翻译失败,错误信息:${error}`);
 
34
  },
35
  },
36
  );
37
+
38
  const summaryText = summaryData.data[0].summary_text as string;
39
+
40
  return summaryText;
41
  } catch (error) {
42
  throw new Error(`总结失败,错误信息:${error}`);
43
  }
44
  }
45
 
46
+ export async function requestQw(data: any) {
47
+ const qwToken = 'sBy1ogROHqapzX0CcdoyjCl$7wAX1NzRpOPRUMrQGgN8J7jSxmMQWreOgkeheTFbylzpf8Gz1g_n0';
48
+
49
+ try {
50
+ const response = await axios.post('https://Joey7938-joe-qw-api.hf.space/api/chat/completions', data, {
51
+ headers: {
52
+ 'Content-Type': 'application/json',
53
+ Authorization: `Bearer ${qwToken}`,
54
+ },
55
+ });
56
+
57
+ return response.data.choices[0].message.content;
58
+ } catch (error: any) {
59
+ throw new Error(error.response.data);
60
+ }
61
+ }
62
+
63
  export async function requestQwImage(data: any) {
64
  const qwToken = 'sBy1ogROHqapzX0CcdoyjCl$7wAX1NzRpOPRUMrQGgN8J7jSxmMQWreOgkeheTFbylzpf8Gz1g_n0';
65