doomiaichat 1.0.2 → 1.1.0

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/dist/index.d.ts CHANGED
@@ -29,6 +29,49 @@ export declare class AIChat extends EventEmitter {
29
29
  * @param {*} s2
30
30
  */
31
31
  getScentenseSimilarity(s1: string, s2: string, axiosOption?: any): Promise<ChatReponse>;
32
+ /**
33
+ * 获得一种内容的相似说法
34
+ * 比如:
35
+ * 你今年多大?
36
+ * 相似问法:您是哪一年出生的
37
+ * 您今年贵庚?
38
+ * @param {*} content
39
+ * @param {需要出来的数量} count
40
+ */
41
+ getSimilarityContent(content: string, count?: number, axiosOption?: AxiosRequestConfig): Promise<ChatReponse>;
42
+ /**
43
+ * 从指定的文本内容中生成相关的问答
44
+ * @param {*} content
45
+ * @param {*} count
46
+ * @param {*} axiosOption
47
+ * @returns
48
+ */ generateQuestionsFromContent(content: string, count?: number, axiosOption?: AxiosRequestConfig): Promise<ChatReponse>;
49
+ /**
50
+ * 解析Faq返回的问题
51
+ * @param {*} messages
52
+ * @returns
53
+ */
54
+ private pickUpFaqContent;
55
+ /**
56
+ * 从指定的文本内容中生成一张试卷
57
+ * @param {*} content
58
+ * @param {试卷的参数} paperOption
59
+ * totalscore: 试卷总分,默认100
60
+ * section: {type:[0,1,2,3]为单选、多选、判断、填空题型 count:生成多少道 score:本段分数}
61
+ * @param {*} axiosOption
62
+ * @returns
63
+ */ generateExaminationPaperFromContent(content: string, paperOption?: any, axiosOption?: AxiosRequestConfig): Promise<ExaminationPaperResult>;
64
+ /**
65
+ * 从答复中得到题目
66
+ * @param {*} result
67
+ *
68
+ */
69
+ private pickUpQuestions;
70
+ /**
71
+ * 将一段很长的文本,按1024长度来划分到多个中
72
+ * @param {*} content
73
+ */
74
+ private splitLongText;
32
75
  }
33
76
  /**
34
77
  * Api封装后的返回结果
@@ -56,8 +99,37 @@ export interface ChatReponse {
56
99
  * 调用OpenAI Api的参数约定
57
100
  */
58
101
  export interface CallChatApiOption {
59
- model?: string;
60
- maxtoken?: number;
61
- temperature?: number;
62
- replyCounts?: number;
102
+ 'model'?: string;
103
+ 'maxtoken'?: number;
104
+ 'temperature'?: number;
105
+ 'replyCounts'?: number;
106
+ }
107
+ /**
108
+ * 调用OpenAI Api的参数约定
109
+ */
110
+ export interface FaqResultResponse {
111
+ 'question': string;
112
+ 'answer'?: string;
113
+ 'keywords'?: Array<string>;
114
+ }
115
+ /**
116
+ * 调用OpenAI Api的参数约定
117
+ */
118
+ export interface ExaminationPaperResult {
119
+ /**
120
+ * return the result of api called
121
+ * @type {boolean}
122
+ */
123
+ 'successed': boolean;
124
+ 'score': number;
125
+ 'paper': any;
126
+ }
127
+ /**
128
+ * 调用OpenAI Api的参数约定
129
+ */
130
+ export interface QuestionItemResultResponse {
131
+ 'question': string;
132
+ 'answer'?: Array<string>;
133
+ 'choice'?: Array<string>;
134
+ 'score'?: number;
63
135
  }
package/dist/index.js CHANGED
@@ -9,6 +9,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { Configuration, OpenAIApi } from "openai";
11
11
  import { EventEmitter } from "events";
12
+ const SECTION_LENGTH = 256; ///每256个字符分成一组
13
+ const MESSAGE_LENGTH = 8; ///每次送8句话给openai 进行解析,送多了,会报错
14
+ //请将答案放在最后,标记为答案:()
15
+ const QUESTION_TEXT_MAPPING = {
16
+ singlechoice: ',根据以上内容,生成1道单选题,每道题目4个选项,请按照{"question":"","choice":[],"answer":[]}的JSON结构输出,choice中的元素用大写字母ABCD开头,answer数组中包含一个正确答案',
17
+ multiplechoice: ',根据以上内容,请生成1道多选题,提供4个选项,答案至少1个以上选项,请按照{"question":"","choice":[],"answer":[]}的JSON结构输出,choice中的元素用大写字母ABCD开头,answer数组中包含正确答案选项',
18
+ trueorfalse: ',根据以上内容,请生成1道判断题,请按照{"question":"","choice":["A.正确","B.错误"],"answer":[]}的JSON结构输出,answer数组中包含一个元素,"正确"或"错误"',
19
+ completion: ',根据以上内容,请生成1道填空题,每道题目1个填空,请按照{"question":"","answer":[]}的JSON结构输出,answer数组中包含填空答案' //请将答案放在最后,标记为答案:()
20
+ };
21
+ const QUESTION_TYPE = ['singlechoice', 'multiplechoice', 'trueorfalse', 'completion'];
12
22
  export class AIChat extends EventEmitter {
13
23
  /**
14
24
  *
@@ -83,4 +93,221 @@ export class AIChat extends EventEmitter {
83
93
  return this.chatRequest(messages, { maxtoken: 32 }, axiosOption);
84
94
  });
85
95
  }
96
+ /**
97
+ * 获得一种内容的相似说法
98
+ * 比如:
99
+ * 你今年多大?
100
+ * 相似问法:您是哪一年出生的
101
+ * 您今年贵庚?
102
+ * @param {*} content
103
+ * @param {需要出来的数量} count
104
+ */
105
+ getSimilarityContent(content, count = 1, axiosOption = {}) {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ let chnReg = /([\u4e00-\u9fa5]|[\ufe30-\uffa0])/.test(content); ///检查源话是否含有中文内容
108
+ let engReg = /[a-zA-Z]/.test(content); ///检查源话是否含有英文内容
109
+ ///如果源话是全中文,那么结果中不应该出来英文的相似说法,如果源话是全英文,则结果不能出现全中文的说法
110
+ let prefix = (!chnReg && engReg) ? '请用完整的英文表达,' : ((chnReg && !engReg) ? '请用完整的中文表达,' : '');
111
+ const text = `${prefix}生成与下面句子意思相同的内容"${content}"`;
112
+ let result = yield this.chatRequest(this.splitLongText(text), { replyCounts: count }, axiosOption);
113
+ if (!result.successed || !result.message)
114
+ return result;
115
+ let replys = result.message.map(item => { return item.message.content.trim(); });
116
+ return { successed: true, message: replys };
117
+ });
118
+ }
119
+ /**
120
+ * 从指定的文本内容中生成相关的问答
121
+ * @param {*} content
122
+ * @param {*} count
123
+ * @param {*} axiosOption
124
+ * @returns
125
+ */ //并在答案末尾处必须给出答案内容中的关键词
126
+ generateQuestionsFromContent(content, count = 1, axiosOption = {}) {
127
+ return __awaiter(this, void 0, void 0, function* () {
128
+ let arrContent = this.splitLongText(content);
129
+ ///没20句话分为一组,适应大文件内容多次请求组合结果
130
+ ///每一句话需要产生的题目
131
+ let questions4EverySentense = count / arrContent.length; //Math.ceil(arrContent.length / 20);
132
+ let faqs = [], gotted = 0;
133
+ while (arrContent.length > 0 && gotted < count) {
134
+ ////每次最多送MESSAGE_LENGTH句话给openai
135
+ let subarray = arrContent.slice(0, MESSAGE_LENGTH);
136
+ let itemCount = Math.min(Math.ceil(subarray.length * questions4EverySentense), count - gotted);
137
+ //subarray.push({ role: 'user', content:'请根据上述内容,给出一道提问与答案以及答案关键词,按照先问题内容,再标准答案,再关键词的顺序输出,关键词之间用、分开'})
138
+ subarray.push({ role: 'user', content: '请根据上述内容,给出一道提问与答案以及答案关键词,按照{"question":"","answer":"","keywords":[]}的JSON结构输出,keywords数组中的关键词必须存在于答案内容中' });
139
+ let result = yield this.chatRequest(subarray, { replyCounts: itemCount }, axiosOption);
140
+ if (result.successed && result.message) {
141
+ let msgs = this.pickUpFaqContent(result.message);
142
+ if (msgs.length) {
143
+ ///对外发送检出问答题的信号
144
+ this.emit('parseout', { type: 'qa', items: msgs });
145
+ gotted += msgs.length; //result.message.length;
146
+ // console.log('gotted=', gotted)
147
+ faqs = faqs.concat(msgs);
148
+ }
149
+ }
150
+ ////删除已经处理的文本
151
+ arrContent.splice(0, MESSAGE_LENGTH);
152
+ }
153
+ arrContent = []; /// 释放内存
154
+ ///发出信号,解析完毕
155
+ this.emit('parseover', { type: 'qa', items: faqs });
156
+ return { successed: true, message: faqs };
157
+ });
158
+ }
159
+ /**
160
+ * 解析Faq返回的问题
161
+ * @param {*} messages
162
+ * @returns
163
+ */
164
+ pickUpFaqContent(messages) {
165
+ let replys = messages.map(item => {
166
+ let content = item.message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
167
+ try {
168
+ let jsonObj = JSON.parse(content);
169
+ if (!Array.isArray(jsonObj.keywords)) {
170
+ jsonObj.keywords = (jsonObj.keywords || '').split(',');
171
+ }
172
+ return jsonObj;
173
+ }
174
+ catch (err) {
175
+ console.log('JSON error', content, err);
176
+ return { question: null };
177
+ }
178
+ });
179
+ return replys.filter(n => { return n.question != null; });
180
+ }
181
+ /**
182
+ * 从指定的文本内容中生成一张试卷
183
+ * @param {*} content
184
+ * @param {试卷的参数} paperOption
185
+ * totalscore: 试卷总分,默认100
186
+ * section: {type:[0,1,2,3]为单选、多选、判断、填空题型 count:生成多少道 score:本段分数}
187
+ * @param {*} axiosOption
188
+ * @returns
189
+ */ //并在答案末尾处必须给出答案内容中的关键词
190
+ generateExaminationPaperFromContent(content, paperOption = {}, axiosOption = {}) {
191
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
192
+ return __awaiter(this, void 0, void 0, function* () {
193
+ let arrContent = this.splitLongText(content);
194
+ let sectionCount = {
195
+ singlechoice: (((_a = paperOption.singlechoice) === null || _a === void 0 ? void 0 : _a.count) || 0) / arrContent.length,
196
+ multiplechoice: (((_b = paperOption.multiplechoice) === null || _b === void 0 ? void 0 : _b.count) || 0) / arrContent.length,
197
+ trueorfalse: (((_c = paperOption.trueorfalse) === null || _c === void 0 ? void 0 : _c.count) || 0) / arrContent.length,
198
+ completion: (((_d = paperOption.completion) === null || _d === void 0 ? void 0 : _d.count) || 0) / arrContent.length
199
+ };
200
+ ///剩余待生成的题目数量
201
+ let remainCount = {
202
+ singlechoice: ((_e = paperOption.singlechoice) === null || _e === void 0 ? void 0 : _e.count) || 0,
203
+ multiplechoice: ((_f = paperOption.multiplechoice) === null || _f === void 0 ? void 0 : _f.count) || 0,
204
+ trueorfalse: ((_g = paperOption.trueorfalse) === null || _g === void 0 ? void 0 : _g.count) || 0,
205
+ completion: ((_h = paperOption.completion) === null || _h === void 0 ? void 0 : _h.count) || 0
206
+ };
207
+ ///每种类型的题目的分数
208
+ let ITEM_SCORE = {
209
+ singlechoice: ((_j = paperOption.singlechoice) === null || _j === void 0 ? void 0 : _j.score) || 0,
210
+ multiplechoice: ((_k = paperOption.multiplechoice) === null || _k === void 0 ? void 0 : _k.score) || 0,
211
+ trueorfalse: ((_l = paperOption.trueorfalse) === null || _l === void 0 ? void 0 : _l.score) || 0,
212
+ completion: ((_m = paperOption.completion) === null || _m === void 0 ? void 0 : _m.score) || 0
213
+ };
214
+ ///最后生成出来的结果
215
+ let paperReturned = {
216
+ singlechoice: [], multiplechoice: [], trueorfalse: [], completion: []
217
+ }, noMoreQuestionRetrive = false, totalscore = 0;
218
+ while (arrContent.length > 0 && !noMoreQuestionRetrive) {
219
+ ////每次最多送MESSAGE_LENGTH句话给openai
220
+ let subarray = arrContent.slice(0, MESSAGE_LENGTH);
221
+ /**
222
+ * 每种类型的题目进行遍历
223
+ */
224
+ noMoreQuestionRetrive = true;
225
+ for (const key of QUESTION_TYPE) {
226
+ ///还需要抓取题目
227
+ if (remainCount[key] > 0) {
228
+ noMoreQuestionRetrive = false;
229
+ subarray.push({ role: 'user', content: QUESTION_TEXT_MAPPING[key] });
230
+ let itemCount = Math.min(remainCount[key], Math.ceil(subarray.length * sectionCount[key]));
231
+ console.log(QUESTION_TEXT_MAPPING[key], itemCount);
232
+ let result = yield this.chatRequest(subarray, { replyCounts: itemCount }, axiosOption);
233
+ if (result.successed && result.message) {
234
+ //console.log('paper result', key, result.message.length)
235
+ let pickedQuestions = this.pickUpQuestions(result.message, key, ITEM_SCORE[key]);
236
+ if (pickedQuestions.length) {
237
+ ///对外发送检出题目的信号
238
+ this.emit('parseout', { type: 'question', name: key, items: pickedQuestions });
239
+ paperReturned[key] = paperReturned[key].concat(pickedQuestions);
240
+ remainCount[key] = remainCount[key] - pickedQuestions.length;
241
+ totalscore = totalscore + pickedQuestions.length * ITEM_SCORE[key];
242
+ }
243
+ }
244
+ subarray.splice(subarray.length - 1, 1); ///把最后的问法删除
245
+ }
246
+ }
247
+ ////删除已经处理的文本
248
+ arrContent.splice(0, MESSAGE_LENGTH);
249
+ }
250
+ ///发出信号,解析完毕
251
+ this.emit('parseover', { type: 'question', items: paperReturned });
252
+ return { successed: true, score: totalscore, paper: paperReturned };
253
+ });
254
+ }
255
+ /**
256
+ * 从答复中得到题目
257
+ * @param {*} result
258
+ *
259
+ */
260
+ pickUpQuestions(result, questiontype, score = 1) {
261
+ let item = result.map(m => {
262
+ ////防止输出的JSON格式不合法
263
+ try {
264
+ let jsonObj = JSON.parse(m.message.content);
265
+ jsonObj.score = score;
266
+ if (jsonObj.choice && Array.isArray(jsonObj.choice) && questiontype != 'completion') {
267
+ jsonObj.fullanswer = (jsonObj.answer + '').replace(/,|[^ABCDE]/g, '');
268
+ jsonObj.choice = jsonObj.choice.map((item, index) => {
269
+ let seqNo = String.fromCharCode(65 + index);
270
+ let correctReg = new RegExp(`${seqNo}.|${seqNo}`, 'ig');
271
+ //let answer = jsonObj.fullanswer
272
+ return {
273
+ id: seqNo,
274
+ content: item.replace(correctReg, '').trim(),
275
+ iscorrect: (jsonObj.fullanswer.indexOf(seqNo) >= 0 || jsonObj.fullanswer.indexOf(m)) >= 0 ? 1 : 0
276
+ };
277
+ });
278
+ }
279
+ switch (questiontype) {
280
+ case 'singlechoice':
281
+ jsonObj.answer = (jsonObj.answer + '').replace(/,|[^ABCDEFG]/g, '').split('').slice(0, 1);
282
+ break;
283
+ case 'multiplechoice':
284
+ jsonObj.answer = (jsonObj.answer + '').replace(/,|[^ABCDEFG]/g, '').split('');
285
+ break;
286
+ case 'trueorfalse':
287
+ jsonObj.answer = [(jsonObj.answer + '').indexOf('正确') >= 0 ? 'A' : 'B'];
288
+ break;
289
+ }
290
+ return jsonObj;
291
+ }
292
+ catch (err) {
293
+ console.log('error happened:', err);
294
+ return null;
295
+ }
296
+ });
297
+ return item.filter(i => { return i != null; });
298
+ }
299
+ /**
300
+ * 将一段很长的文本,按1024长度来划分到多个中
301
+ * @param {*} content
302
+ */
303
+ splitLongText(content, len = SECTION_LENGTH) {
304
+ let start = 0, message = [], length = content.length;
305
+ while (start < length) {
306
+ const subtext = content.substr(start, len);
307
+ if (subtext)
308
+ message.push({ role: 'user', content: subtext });
309
+ start += len;
310
+ }
311
+ return message;
312
+ }
86
313
  }
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "doomiaichat",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Doomisoft OpenAI",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "build": "tsc"
8
8
  },
9
- "keywords": [
10
- "ChatGpt"
11
- ],
9
+ "keywords": ["ChatGpt"],
12
10
  "author": "Stephen.Shen",
13
11
  "license": "ISC",
14
12
  "devDependencies": {
package/src/index.ts CHANGED
@@ -1,6 +1,17 @@
1
1
  import { Configuration, OpenAIApi, CreateChatCompletionResponse, ChatCompletionRequestMessage } from "openai"
2
2
  import { EventEmitter } from "events";
3
3
  import { AxiosRequestConfig, AxiosResponse } from "axios";
4
+ const SECTION_LENGTH = 256; ///每256个字符分成一组
5
+ const MESSAGE_LENGTH = 8; ///每次送8句话给openai 进行解析,送多了,会报错
6
+ //请将答案放在最后,标记为答案:()
7
+ const QUESTION_TEXT_MAPPING: any = {
8
+ singlechoice: ',根据以上内容,生成1道单选题,每道题目4个选项,请按照{"question":"","choice":[],"answer":[]}的JSON结构输出,choice中的元素用大写字母ABCD开头,answer数组中包含一个正确答案',
9
+ multiplechoice: ',根据以上内容,请生成1道多选题,提供4个选项,答案至少1个以上选项,请按照{"question":"","choice":[],"answer":[]}的JSON结构输出,choice中的元素用大写字母ABCD开头,answer数组中包含正确答案选项', //请将答案放在最后,标记为答案:()
10
+ trueorfalse: ',根据以上内容,请生成1道判断题,请按照{"question":"","choice":["A.正确","B.错误"],"answer":[]}的JSON结构输出,answer数组中包含一个元素,"正确"或"错误"', //标记为答案:(正确或错误)
11
+ completion: ',根据以上内容,请生成1道填空题,每道题目1个填空,请按照{"question":"","answer":[]}的JSON结构输出,answer数组中包含填空答案' //请将答案放在最后,标记为答案:()
12
+ }
13
+ const QUESTION_TYPE:Array<string> = ['singlechoice', 'multiplechoice', 'trueorfalse', 'completion']
14
+
4
15
  export class AIChat extends EventEmitter {
5
16
  private readonly chatRobot: OpenAIApi;
6
17
  private readonly chatModel: string;
@@ -75,6 +86,220 @@ export class AIChat extends EventEmitter {
75
86
  ]
76
87
  return this.chatRequest(messages, {maxtoken:32}, axiosOption);
77
88
  }
89
+
90
+
91
+ /**
92
+ * 获得一种内容的相似说法
93
+ * 比如:
94
+ * 你今年多大?
95
+ * 相似问法:您是哪一年出生的
96
+ * 您今年贵庚?
97
+ * @param {*} content
98
+ * @param {需要出来的数量} count
99
+ */
100
+ async getSimilarityContent(content:string, count:number = 1, axiosOption:AxiosRequestConfig = {}):Promise<ChatReponse> {
101
+ let chnReg:boolean = /([\u4e00-\u9fa5]|[\ufe30-\uffa0])/.test(content) ///检查源话是否含有中文内容
102
+ let engReg: boolean = /[a-zA-Z]/.test(content) ///检查源话是否含有英文内容
103
+ ///如果源话是全中文,那么结果中不应该出来英文的相似说法,如果源话是全英文,则结果不能出现全中文的说法
104
+ let prefix = (!chnReg && engReg) ? '请用完整的英文表达,' : ((chnReg && !engReg) ? '请用完整的中文表达,':'')
105
+ const text = `${prefix}生成与下面句子意思相同的内容"${content}"`
106
+ let result = await this.chatRequest(this.splitLongText(text), { replyCounts: count }, axiosOption);
107
+ if (!result.successed || !result.message) return result;
108
+ let replys = result.message.map(item => { return item.message.content.trim(); })
109
+ return { successed: true, message: replys }
110
+ }
111
+
112
+ /**
113
+ * 从指定的文本内容中生成相关的问答
114
+ * @param {*} content
115
+ * @param {*} count
116
+ * @param {*} axiosOption
117
+ * @returns
118
+ *///并在答案末尾处必须给出答案内容中的关键词
119
+ async generateQuestionsFromContent(content:string, count:number = 1, axiosOption:AxiosRequestConfig = {}):Promise<ChatReponse> {
120
+ let arrContent = this.splitLongText(content);
121
+ ///没20句话分为一组,适应大文件内容多次请求组合结果
122
+ ///每一句话需要产生的题目
123
+ let questions4EverySentense:number = count / arrContent.length; //Math.ceil(arrContent.length / 20);
124
+ let faqs:Array<FaqResultResponse> = [], gotted:number = 0;
125
+ while (arrContent.length > 0 && gotted < count) {
126
+ ////每次最多送MESSAGE_LENGTH句话给openai
127
+ let subarray = arrContent.slice(0, MESSAGE_LENGTH);
128
+ let itemCount = Math.min(Math.ceil(subarray.length * questions4EverySentense), count - gotted);
129
+ //subarray.push({ role: 'user', content:'请根据上述内容,给出一道提问与答案以及答案关键词,按照先问题内容,再标准答案,再关键词的顺序输出,关键词之间用、分开'})
130
+ subarray.push({ role: 'user', content: '请根据上述内容,给出一道提问与答案以及答案关键词,按照{"question":"","answer":"","keywords":[]}的JSON结构输出,keywords数组中的关键词必须存在于答案内容中' })
131
+ let result = await this.chatRequest(subarray, { replyCounts: itemCount} , axiosOption);
132
+ if (result.successed && result.message) {
133
+ let msgs = this.pickUpFaqContent(result.message);
134
+ if (msgs.length) {
135
+ ///对外发送检出问答题的信号
136
+ this.emit('parseout', { type: 'qa', items: msgs })
137
+ gotted += msgs.length; //result.message.length;
138
+ // console.log('gotted=', gotted)
139
+ faqs = faqs.concat(msgs);
140
+ }
141
+
142
+ }
143
+ ////删除已经处理的文本
144
+ arrContent.splice(0, MESSAGE_LENGTH);
145
+
146
+ }
147
+ arrContent = []; /// 释放内存
148
+ ///发出信号,解析完毕
149
+ this.emit('parseover', { type: 'qa', items: faqs })
150
+ return { successed: true, message: faqs };
151
+ }
152
+ /**
153
+ * 解析Faq返回的问题
154
+ * @param {*} messages
155
+ * @returns
156
+ */
157
+ private pickUpFaqContent(messages: Array<any>): Array<FaqResultResponse> {
158
+ let replys = messages.map(item => {
159
+ let content = item.message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
160
+ try {
161
+ let jsonObj = JSON.parse(content);
162
+ if (!Array.isArray(jsonObj.keywords)) {
163
+ jsonObj.keywords = (jsonObj.keywords || '').split(',')
164
+ }
165
+ return jsonObj ;
166
+ } catch (err) {
167
+ console.log('JSON error', content, err)
168
+ return {question:null};
169
+ }
170
+ })
171
+ return replys.filter(n => { return n.question != null });
172
+ }
173
+
174
+ /**
175
+ * 从指定的文本内容中生成一张试卷
176
+ * @param {*} content
177
+ * @param {试卷的参数} paperOption
178
+ * totalscore: 试卷总分,默认100
179
+ * section: {type:[0,1,2,3]为单选、多选、判断、填空题型 count:生成多少道 score:本段分数}
180
+ * @param {*} axiosOption
181
+ * @returns
182
+ *///并在答案末尾处必须给出答案内容中的关键词
183
+ async generateExaminationPaperFromContent(content: string, paperOption: any = {}, axiosOption: AxiosRequestConfig = {}): Promise<ExaminationPaperResult> {
184
+ let arrContent = this.splitLongText(content);
185
+ let sectionCount: any = {
186
+ singlechoice: (paperOption.singlechoice?.count || 0) / arrContent.length,
187
+ multiplechoice: (paperOption.multiplechoice?.count || 0) / arrContent.length,
188
+ trueorfalse: (paperOption.trueorfalse?.count || 0) / arrContent.length,
189
+ completion: (paperOption.completion?.count || 0) / arrContent.length
190
+ };
191
+ ///剩余待生成的题目数量
192
+ let remainCount:any = {
193
+ singlechoice:paperOption.singlechoice?.count || 0,
194
+ multiplechoice: paperOption.multiplechoice?.count || 0,
195
+ trueorfalse: paperOption.trueorfalse?.count || 0,
196
+ completion: paperOption.completion?.count || 0
197
+ };
198
+ ///每种类型的题目的分数
199
+ let ITEM_SCORE: any = {
200
+ singlechoice: paperOption.singlechoice?.score || 0,
201
+ multiplechoice: paperOption.multiplechoice?.score || 0,
202
+ trueorfalse: paperOption.trueorfalse?.score || 0,
203
+ completion: paperOption.completion?.score || 0
204
+ };
205
+ ///最后生成出来的结果
206
+ let paperReturned: any = {
207
+ singlechoice: [], multiplechoice: [], trueorfalse: [], completion: []
208
+
209
+ }, noMoreQuestionRetrive:boolean = false, totalscore:number = 0;
210
+
211
+ while (arrContent.length > 0 && !noMoreQuestionRetrive) {
212
+ ////每次最多送MESSAGE_LENGTH句话给openai
213
+ let subarray = arrContent.slice(0, MESSAGE_LENGTH);
214
+ /**
215
+ * 每种类型的题目进行遍历
216
+ */
217
+ noMoreQuestionRetrive = true;
218
+ for (const key of QUESTION_TYPE) {
219
+ ///还需要抓取题目
220
+ if (remainCount[key] > 0) {
221
+ noMoreQuestionRetrive = false;
222
+ subarray.push({ role: 'user', content: QUESTION_TEXT_MAPPING[key] })
223
+ let itemCount = Math.min(remainCount[key], Math.ceil(subarray.length * sectionCount[key]));
224
+ console.log(QUESTION_TEXT_MAPPING[key], itemCount);
225
+ let result = await this.chatRequest(subarray, { replyCounts: itemCount } , axiosOption);
226
+ if (result.successed && result.message) {
227
+ //console.log('paper result', key, result.message.length)
228
+ let pickedQuestions = this.pickUpQuestions(result.message, key, ITEM_SCORE[key]);
229
+ if (pickedQuestions.length) {
230
+ ///对外发送检出题目的信号
231
+ this.emit('parseout', { type: 'question', name: key, items: pickedQuestions })
232
+ paperReturned[key] = paperReturned[key].concat(pickedQuestions);
233
+ remainCount[key] = remainCount[key] - pickedQuestions.length;
234
+ totalscore = totalscore + pickedQuestions.length * ITEM_SCORE[key];
235
+ }
236
+ }
237
+ subarray.splice(subarray.length - 1, 1); ///把最后的问法删除
238
+ }
239
+ }
240
+ ////删除已经处理的文本
241
+ arrContent.splice(0, MESSAGE_LENGTH);
242
+ }
243
+ ///发出信号,解析完毕
244
+ this.emit('parseover', { type: 'question', items: paperReturned })
245
+ return { successed: true, score: totalscore, paper: paperReturned }
246
+ }
247
+ /**
248
+ * 从答复中得到题目
249
+ * @param {*} result
250
+ *
251
+ */
252
+ private pickUpQuestions(result: Array<any>, questiontype: string, score: number = 1): Array<QuestionItemResultResponse> {
253
+ let item = result.map(m => {
254
+ ////防止输出的JSON格式不合法
255
+ try {
256
+ let jsonObj = JSON.parse(m.message.content)
257
+ jsonObj.score = score;
258
+ if (jsonObj.choice && Array.isArray(jsonObj.choice) && questiontype != 'completion') {
259
+ jsonObj.fullanswer = (jsonObj.answer + '').replace(/,|[^ABCDE]/g, '');
260
+ jsonObj.choice = jsonObj.choice.map((item:string, index:number) => {
261
+ let seqNo = String.fromCharCode(65 + index);
262
+ let correctReg = new RegExp(`${seqNo}.|${seqNo}`, 'ig')
263
+ //let answer = jsonObj.fullanswer
264
+ return {
265
+ id: seqNo,
266
+ content: item.replace(correctReg, '').trim(),
267
+ iscorrect: (jsonObj.fullanswer.indexOf(seqNo) >= 0 || jsonObj.fullanswer.indexOf(m)) >= 0 ? 1 : 0
268
+ }
269
+ })
270
+ }
271
+ switch (questiontype) {
272
+ case 'singlechoice':
273
+ jsonObj.answer = (jsonObj.answer + '').replace(/,|[^ABCDEFG]/g, '').split('').slice(0, 1);
274
+ break;
275
+ case 'multiplechoice':
276
+ jsonObj.answer = (jsonObj.answer + '').replace(/,|[^ABCDEFG]/g, '').split('');
277
+ break;
278
+ case 'trueorfalse':
279
+ jsonObj.answer = [(jsonObj.answer + '').indexOf('正确') >= 0 ? 'A' : 'B']
280
+ break;
281
+ }
282
+ return jsonObj;
283
+ } catch (err) {
284
+ console.log('error happened:', err);
285
+ return null;
286
+ }
287
+ })
288
+ return item.filter(i => { return i != null; });
289
+ }
290
+ /**
291
+ * 将一段很长的文本,按1024长度来划分到多个中
292
+ * @param {*} content
293
+ */
294
+ private splitLongText(content: string, len = SECTION_LENGTH): Array<ChatCompletionRequestMessage> {
295
+ let start = 0, message: Array<ChatCompletionRequestMessage> = [], length = content.length;
296
+ while (start < length) {
297
+ const subtext = content.substr(start, len);
298
+ if (subtext) message.push({ role: 'user', content: subtext })
299
+ start += len;
300
+ }
301
+ return message;
302
+ }
78
303
  }
79
304
 
80
305
  /**
@@ -103,8 +328,40 @@ export interface ChatReponse {
103
328
  * 调用OpenAI Api的参数约定
104
329
  */
105
330
  export interface CallChatApiOption{
106
- model?:string, ///模型名称
107
- maxtoken?:number; ///返回的最大token
108
- temperature?:number;
109
- replyCounts?:number; ///返回多少答案
110
- }
331
+ 'model'?:string, ///模型名称
332
+ 'maxtoken'?:number; ///返回的最大token
333
+ 'temperature'?:number;
334
+ 'replyCounts'?:number; ///返回多少答案
335
+ }
336
+
337
+ /**
338
+ * 调用OpenAI Api的参数约定
339
+ */
340
+ export interface FaqResultResponse {
341
+ 'question': string, ///模型名称
342
+ 'answer'?: string; ///返回的最大token
343
+ 'keywords'?: Array<string>;
344
+ }
345
+
346
+ /**
347
+ * 调用OpenAI Api的参数约定
348
+ */
349
+ export interface ExaminationPaperResult {
350
+ /**
351
+ * return the result of api called
352
+ * @type {boolean}
353
+ */
354
+ 'successed': boolean,
355
+ 'score': number, ///卷面总分
356
+ 'paper': any
357
+ }
358
+ /**
359
+ * 调用OpenAI Api的参数约定
360
+ */
361
+ export interface QuestionItemResultResponse {
362
+ 'question': string, ///模型名称
363
+ 'answer'?: Array<string>; ///返回的最大token
364
+ 'choice'?: Array<string>;
365
+ 'score'?:number;
366
+ }
367
+