ctod 0.0.5 → 0.0.7

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.
@@ -0,0 +1,129 @@
1
+ import { flow, array } from 'power-helper'
2
+ import { ImagesGenerations } from '../../lib/service/images-generations'
3
+ import { definedOutputDir, getKey } from '../utils'
4
+ import { ChatGPT35Broker, plugins, templates } from '../../lib/index'
5
+
6
+ /**
7
+ * @invoke npx ts-node ./examples/applications/story-generations.ts
8
+ */
9
+
10
+ const styles = ['Adventure', 'Romance', 'Horror', 'Science Fiction', 'Suspense', 'Comedy', 'Inspirational', 'Epic', 'Philosophical', 'Historical', 'Supernatural', 'Crime', 'Family', 'Classic', 'Fairy Tale', 'Animal', 'Science', 'War', 'Fantasy', 'Social', 'Romantic', 'Short Story', 'Novel', 'Narrative Poem', 'Lyric Poem', 'Epic Poem', 'Variations', 'Memoir', 'Autobiography', 'Fantasy Fiction', 'Political', 'Romantic Comedy', 'Science Fiction Adventure', 'Crime Thriller', 'Pure Love', 'Mythology', 'Supernatural Fiction', 'Horror Comedy', 'Epic Fantasy', 'Surrealism', 'Satire', 'Dystopian Fiction', 'Humorous Poetry', 'Philosophical Fiction', 'Suspense Thriller', 'Historical Epic', 'Romantic Suspense', 'Humor', 'Science Fiction Romance', 'Historical Romance']
11
+ const ends = ['Happy ending', 'Tragic ending', 'Open ending', 'Bittersweet ending', 'Ambiguous ending', 'Surprise ending', 'Cathartic ending', 'Redemptive ending', 'Moral ending', 'Ironic ending', 'Deus ex machina', 'Twist ending', 'Epiphany ending', 'Mystery ending', 'Hopeful ending', 'Pessimistic ending', 'Revenge ending', 'Enigmatic ending', 'Metaphorical ending', 'Reflective ending', 'Satisfying ending', 'Unresolved ending', 'Transformation ending', 'Resolution ending', 'Sacrificial ending', 'Indeterminate ending', 'Existential ending', 'Nihilistic ending', 'Tragicomedy ending', 'Inconclusive ending', 'Climactic ending', 'Peripeteia ending', 'Prophetic ending', 'Heartwarming ending', 'Disillusioned ending', 'Sardonic ending', 'Revelatory ending', 'Comedic ending', 'Dramatic ending', 'Fateful ending', 'Introspective ending', 'Reconciling ending', 'Uplifting ending', 'Chaotic ending', 'Fatalistic ending', 'Epilogue ending', 'Foreboding ending', 'Satirical ending', 'Surreal ending']
12
+
13
+ const genImage = async (params: {
14
+ style: string
15
+ title: string
16
+ apiKey: string
17
+ outline: string
18
+ }) => {
19
+ const bot = new ImagesGenerations()
20
+ bot.setConfiguration(params.apiKey)
21
+ bot.setConfig({
22
+ n: 1,
23
+ size: '512x512'
24
+ })
25
+ const result = await bot.create(`is a ${params.style} style story, subject is ${params.title}, ${params.outline}`)
26
+ return result.data.map(e => e.b64_json)
27
+ }
28
+
29
+ const genStory = async (params: {
30
+ apiKey: string
31
+ style: string
32
+ ending: string
33
+ }) => {
34
+ const broker = new ChatGPT35Broker({
35
+ input: yup => {
36
+ return {
37
+ ending: yup.string().required(),
38
+ style: yup.string().required()
39
+ }
40
+ },
41
+ output: yup => {
42
+ return {
43
+ enUsOutline: yup.string().required(),
44
+ zhTwOutline: yup.string().required(),
45
+ enUsTitle: yup.string().required(),
46
+ zhTwTitle: yup.string().required(),
47
+ enUsContent: yup.array(yup.string()).required(),
48
+ zhTwContent: yup.array(yup.string()).required()
49
+ }
50
+ },
51
+ plugins: [
52
+ plugins.PrintLogPlugin.ver35.use({})
53
+ ],
54
+ install: ({ bot, attach }) => {
55
+ bot.setConfiguration(params.apiKey)
56
+ attach('parseFailed', async({ count, retry, response, changeMessages }) => {
57
+ if (count <= 5) {
58
+ console.log(`回傳錯誤,正在重試: ${count} 次`)
59
+ changeMessages([response.newMessages[0]])
60
+ retry()
61
+ }
62
+ })
63
+ },
64
+ assembly: async ({ style, ending }) => {
65
+ return templates.requireJsonResponse([
66
+ `生成一個 ${style} 風格的故事,必須是 ${ending} 結局`,
67
+ '內容請豐富精彩有深度,而且有起承轉合,符合電影的三段結構,必須1000字以上',
68
+ '有中英兩種版本,中文是繁體中文'
69
+ ], {
70
+ enUsOutline: {
71
+ desc: '故事簡述,請用英文呈現',
72
+ example: 'string'
73
+ },
74
+ zhTwOutline: {
75
+ desc: '故事簡述,請用中文呈現',
76
+ example: 'string'
77
+ },
78
+ enUsTitle: {
79
+ desc: '英文標題',
80
+ example: 'string'
81
+ },
82
+ zhTwTitle: {
83
+ desc: '中文標題',
84
+ example: 'string'
85
+ },
86
+ enUsContent: {
87
+ desc: '英文內容,每一段為一個元素',
88
+ example: 'string[]'
89
+ },
90
+ zhTwContent: {
91
+ desc: '中文內容,每一段為一個元素',
92
+ example: 'string[]'
93
+ }
94
+ })
95
+ }
96
+ })
97
+ const result = await broker.request(params)
98
+ return result
99
+ }
100
+
101
+ flow.run(async () => {
102
+ const apiKey = await getKey()
103
+ const style = array.randomPick(styles)
104
+ const ending = array.randomPick(ends)
105
+ try {
106
+ const result = await genStory({
107
+ style,
108
+ ending,
109
+ apiKey
110
+ })
111
+ const dir = definedOutputDir(`.output/stores/${result.zhTwTitle}`)
112
+ dir.write('story.json', result)
113
+ dir.write('story-config.json', {
114
+ style,
115
+ ending
116
+ })
117
+ for (let i = 0; i < 2; i++) {
118
+ const images = await genImage({
119
+ style,
120
+ apiKey,
121
+ title: result.enUsTitle,
122
+ outline: result.enUsOutline
123
+ })
124
+ dir.write(`cover-${i}.png`, images[0])
125
+ }
126
+ } catch (error: any) {
127
+ console.error('Error:', error?.response?.data?.error?.message ?? error)
128
+ }
129
+ })
@@ -0,0 +1,255 @@
1
+ import { flow, array } from 'power-helper'
2
+ import { ImagesGenerations } from '../../lib/service/images-generations'
3
+ import { definedOutputDir, getKey } from '../utils'
4
+ import { ChatGPT35Broker, plugins, templates } from '../../lib/index'
5
+
6
+ /**
7
+ * @invoke npx ts-node ./examples/applications/talk-generations.ts
8
+ */
9
+
10
+ const situations = [
11
+ {
12
+ 'en-US': 'Brainstorming session between marketing team',
13
+ 'zh-TW': '行銷團隊頭腦風暴會議'
14
+ },
15
+ {
16
+ 'en-US': 'Customer service representative handling a complaint',
17
+ 'zh-TW': '客服代表處理客訴'
18
+ },
19
+ {
20
+ 'en-US': 'Job interview for a software engineer position',
21
+ 'zh-TW': '軟體工程師職位面試'
22
+ },
23
+ {
24
+ 'en-US': 'Negotiation between a vendor and a client',
25
+ 'zh-TW': '供應商與客戶的談判'
26
+ },
27
+ {
28
+ 'en-US': 'Meeting between a designer and a developer',
29
+ 'zh-TW': '設計師與開發人員的會議'
30
+ },
31
+ {
32
+ 'en-US': 'Team huddle before a sports game',
33
+ 'zh-TW': '比賽前的團隊集合'
34
+ },
35
+ {
36
+ 'en-US': 'Consultation with a financial advisor',
37
+ 'zh-TW': '財務顧問諮詢'
38
+ },
39
+ {
40
+ 'en-US': 'Parent-teacher conference',
41
+ 'zh-TW': '家長教師會議'
42
+ },
43
+ {
44
+ 'en-US': 'Discussion between a doctor and a patient',
45
+ 'zh-TW': '醫生與病人的討論'
46
+ },
47
+ {
48
+ 'en-US': 'Debate between political candidates',
49
+ 'zh-TW': '政治候選人辯論'
50
+ }
51
+ ]
52
+
53
+ const tones = [
54
+ {
55
+ 'en-US': 'Angry',
56
+ 'zh-TW': '憤怒'
57
+ },
58
+ {
59
+ 'en-US': 'Excited',
60
+ 'zh-TW': '興奮'
61
+ },
62
+ {
63
+ 'en-US': 'Bored',
64
+ 'zh-TW': '無聊'
65
+ },
66
+ {
67
+ 'en-US': 'Sad',
68
+ 'zh-TW': '悲傷'
69
+ },
70
+ {
71
+ 'en-US': 'Happy',
72
+ 'zh-TW': '快樂'
73
+ },
74
+ {
75
+ 'en-US': 'Confused',
76
+ 'zh-TW': '困惑'
77
+ },
78
+ {
79
+ 'en-US': 'Frustrated',
80
+ 'zh-TW': '挫敗'
81
+ },
82
+ {
83
+ 'en-US': 'Curious',
84
+ 'zh-TW': '好奇'
85
+ },
86
+ {
87
+ 'en-US': 'Nervous',
88
+ 'zh-TW': '緊張'
89
+ },
90
+ {
91
+ 'en-US': 'Relaxed',
92
+ 'zh-TW': '輕鬆'
93
+ }
94
+ ]
95
+
96
+ const styles = [
97
+ {
98
+ 'en-US': 'Humorous',
99
+ 'zh-TW': '幽默'
100
+ },
101
+ {
102
+ 'en-US': 'Formal',
103
+ 'zh-TW': '正式'
104
+ },
105
+ {
106
+ 'en-US': 'Casual',
107
+ 'zh-TW': '隨意'
108
+ },
109
+ {
110
+ 'en-US': 'Professional',
111
+ 'zh-TW': '專業'
112
+ },
113
+ {
114
+ 'en-US': 'Friendly',
115
+ 'zh-TW': '友善'
116
+ },
117
+ {
118
+ 'en-US': 'Serious',
119
+ 'zh-TW': '嚴肅'
120
+ },
121
+ {
122
+ 'en-US': 'Sarcastic',
123
+ 'zh-TW': '諷刺'
124
+ },
125
+ {
126
+ 'en-US': 'Inquisitive',
127
+ 'zh-TW': '探究'
128
+ },
129
+ {
130
+ 'en-US': 'Persuasive',
131
+ 'zh-TW': '說服'
132
+ },
133
+ {
134
+ 'en-US': 'Encouraging',
135
+ 'zh-TW': '鼓勵'
136
+ }
137
+ ]
138
+
139
+ const genImage = async (params: {
140
+ tone: string
141
+ apiKey: string,
142
+ style: string
143
+ situation: string
144
+ }) => {
145
+ const bot = new ImagesGenerations()
146
+ bot.setConfiguration(params.apiKey)
147
+ bot.setConfig({
148
+ n: 1,
149
+ size: '512x512'
150
+ })
151
+ const result = await bot.create(`two people ${params.tone} ${params.situation}, ${params.style}`)
152
+ return result.data.map(e => e.b64_json)
153
+ }
154
+
155
+ const genTalk = async (params: {
156
+ apiKey: string
157
+ tone: string
158
+ style: string
159
+ situation: string
160
+ }) => {
161
+ const broker = new ChatGPT35Broker({
162
+ input: yup => {
163
+ return {
164
+ tone: yup.string().required(),
165
+ style: yup.string().required(),
166
+ situation: yup.string().required()
167
+ }
168
+ },
169
+ output: yup => {
170
+ return {
171
+ enUsOutline: yup.string().required(),
172
+ zhTwOutline: yup.string().required(),
173
+ enUsTitle: yup.string().required(),
174
+ zhTwTitle: yup.string().required(),
175
+ enUsContent: yup.array(yup.string()).required(),
176
+ zhTwContent: yup.array(yup.string()).required()
177
+ }
178
+ },
179
+ plugins: [
180
+ plugins.PrintLogPlugin.ver35.use({})
181
+ ],
182
+ install: ({ bot, attach }) => {
183
+ bot.setConfiguration(params.apiKey)
184
+ attach('parseFailed', async ({ count, retry, response, changeMessages }) => {
185
+ if (count <= 5) {
186
+ console.log(`回傳錯誤,正在重試: ${count} 次`)
187
+ changeMessages([response.newMessages[0]])
188
+ retry()
189
+ }
190
+ })
191
+ },
192
+ assembly: async ({ situation, style, tone }) => {
193
+ return templates.requireJsonResponse([
194
+ `幫我生成一組以${style}為出發點的對話`,
195
+ `是兩個 ${tone} 的人進行,${situation}`,
196
+ '有中英兩種版本,中文是繁體中文'
197
+ ], {
198
+ enUsOutline: {
199
+ desc: '故事簡述,請用英文呈現',
200
+ example: 'string'
201
+ },
202
+ zhTwOutline: {
203
+ desc: '故事簡述,請用中文呈現',
204
+ example: 'string'
205
+ },
206
+ enUsTitle: {
207
+ desc: '英文標題',
208
+ example: 'string'
209
+ },
210
+ zhTwTitle: {
211
+ desc: '中文標題',
212
+ example: 'string'
213
+ },
214
+ enUsContent: {
215
+ desc: '英文內容,每一段為一個元素',
216
+ example: 'string[]'
217
+ },
218
+ zhTwContent: {
219
+ desc: '中文內容,每一段為一個元素',
220
+ example: 'string[]'
221
+ }
222
+ })
223
+ }
224
+ })
225
+ const result = await broker.request(params)
226
+ return result
227
+ }
228
+
229
+ flow.run(async () => {
230
+ const apiKey = await getKey()
231
+ const tone = array.randomPick(tones)
232
+ const style = array.randomPick(styles)
233
+ const situation = array.randomPick(situations)
234
+ try {
235
+ const result = await genTalk({
236
+ tone: tone['en-US'],
237
+ situation: situation['en-US'],
238
+ style: style['en-US'],
239
+ apiKey
240
+ })
241
+ const images = await genImage({
242
+ tone: tone['en-US'],
243
+ situation: situation['en-US'],
244
+ style: style['en-US'],
245
+ apiKey
246
+ })
247
+ const dir = definedOutputDir(`.output/talks/${result.zhTwTitle}`)
248
+ dir.write('content.json', result)
249
+ for (let image of images) {
250
+ dir.write(`cover-${Date.now()}.png`, image, 'base64')
251
+ }
252
+ } catch (error: any) {
253
+ console.error('Error:', error?.response?.data?.error?.message ?? error)
254
+ }
255
+ })
@@ -8,6 +8,7 @@ import { ChatGPT35Broker, plugins, templates } from '../lib/index'
8
8
  * @test npx ts-node ./examples/chatgpt3.5-broker.ts
9
9
  */
10
10
 
11
+
11
12
  flow.run(async() => {
12
13
  const { apiKey } = await prompt([
13
14
  {
@@ -0,0 +1,56 @@
1
+ import fs from 'fs'
2
+ import { Buffer } from 'buffer'
3
+ import { prompt } from 'inquirer'
4
+
5
+ type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'latin1' | 'binary' | 'hex'
6
+
7
+ /**
8
+ * @zh 你可以透過在根目錄中加入 .key 檔案,來自動讀取 API key
9
+ * @en You can automatically read the API key by adding the .key file in the root directory.
10
+ */
11
+
12
+ export const getKey = async() => {
13
+ let apiKey = ''
14
+ let keyFile = './.key'
15
+ if (fs.existsSync(keyFile)) {
16
+ apiKey = fs.readFileSync('./.key').toString()
17
+ }
18
+ if (!apiKey) {
19
+ const { inputApiKey } = await prompt([
20
+ {
21
+ type: 'input',
22
+ name: 'inputApiKey',
23
+ message: 'Please enter API key.',
24
+ default: ''
25
+ }
26
+ ])
27
+ apiKey = inputApiKey
28
+ }
29
+ if (!apiKey) {
30
+ throw new Error('Unable to find API key.')
31
+ }
32
+ return apiKey
33
+ }
34
+
35
+ /**
36
+ * @zh 定義輸出資料夾位置
37
+ * @en Define the output folder location
38
+ */
39
+
40
+ export const definedOutputDir = (dirName: string) => {
41
+ let dir = `.output/${dirName}`
42
+ if (fs.existsSync(dir) === false) {
43
+ fs.mkdirSync(dir, {
44
+ recursive: true
45
+ })
46
+ }
47
+ return {
48
+ write(fileName: string, content: Buffer | string | Record<any, any>, encode: BufferEncoding = 'utf-8') {
49
+ let data = content
50
+ if (Buffer.isBuffer(content) === false && typeof content !== 'string') {
51
+ data = JSON.stringify(content, null, 4)
52
+ }
53
+ fs.writeFileSync(`${dir}/${fileName}`, data, encode)
54
+ }
55
+ }
56
+ }
package/lib/broker/3.ts CHANGED
@@ -9,21 +9,49 @@ export class ChatGPT3Broker<
9
9
  S extends ValidateCallback<any>,
10
10
  O extends ValidateCallback<any>,
11
11
  > extends BaseBroker<S, O, Broker3Plugin<any>, {
12
+
13
+ /**
14
+ * @zh 發送聊天訊息給機器人前觸發
15
+ * @en Triggered before sending chat message to bot
16
+ */
17
+
12
18
  talkBefore: {
19
+ id: string
13
20
  data: ValidateCallbackOutputs<S>
14
21
  prompt: string
15
22
  }
23
+
24
+ /**
25
+ * @zh 當聊天機器人回傳資料的時候觸發
26
+ * @en Triggered when the chatbot returns data
27
+ */
28
+
16
29
  talkAfter: {
30
+ id: string
17
31
  data: ValidateCallbackOutputs<S>
18
32
  prompt: string
19
33
  response: ChatGPT3TalkResponse
20
34
  parseText: string
21
35
  changeParseText: (text: string) => void
22
36
  }
37
+
38
+ /**
39
+ * @zh 當回傳資料符合規格時觸發
40
+ * @en Triggered when the returned data meets the specifications
41
+ */
42
+
23
43
  succeeded: {
44
+ id: string
24
45
  output: ValidateCallbackOutputs<O>
25
46
  }
47
+
48
+ /**
49
+ * @zh 當回傳資料不符合規格,或是解析錯誤時觸發
50
+ * @en Triggered when the returned data does not meet the specifications or parsing errors
51
+ */
52
+
26
53
  parseFailed: {
54
+ id: string
27
55
  error: any
28
56
  count: number
29
57
  retry: () => void
@@ -31,12 +59,27 @@ export class ChatGPT3Broker<
31
59
  parserFails: { name: string, error: any }[]
32
60
  changePrompt: (text: string) => void
33
61
  }
62
+
63
+ /**
64
+ * @zh 不論成功失敗,執行結束的時候會執行。
65
+ * @en It will be executed when the execution is completed, regardless of success or failure.
66
+ */
67
+
68
+ done: {
69
+ id: string
70
+ }
34
71
  }> {
35
72
 
36
73
  bot: ChatGPT3 = new ChatGPT3()
37
74
 
75
+ /**
76
+ * @zh 將請求發出至聊天機器人。
77
+ * @en Send request to chatbot.
78
+ */
79
+
38
80
  async request<T extends Translator<S, O>>(data: T['__schemeType']): Promise<T['__outputType']> {
39
81
  this._install()
82
+ let id = flow.createUuid()
40
83
  let output: any = null
41
84
  let question = await this.translator.compile(data)
42
85
  await flow.asyncWhile(async ({ count, doBreak }) => {
@@ -48,12 +91,14 @@ export class ChatGPT3Broker<
48
91
  let retryFlag = false
49
92
  try {
50
93
  await this.hook.notify('talkBefore', {
94
+ id,
51
95
  data,
52
96
  prompt: question.prompt
53
97
  })
54
98
  response = await this.bot.talk(question.prompt)
55
99
  parseText = response.text
56
100
  await this.hook.notify('talkAfter', {
101
+ id,
57
102
  data,
58
103
  prompt: question.prompt,
59
104
  response,
@@ -64,13 +109,16 @@ export class ChatGPT3Broker<
64
109
  })
65
110
  output = (await this.translator.parse(parseText)).output
66
111
  await this.hook.notify('succeeded', {
112
+ id,
67
113
  output
68
114
  })
115
+ await this.hook.notify('done', { id })
69
116
  doBreak()
70
117
  } catch (error: any) {
71
118
  // 如果解析錯誤,可以選擇是否重新解讀
72
119
  if (error.isParserError) {
73
120
  await this.hook.notify('parseFailed', {
121
+ id,
74
122
  error: error.error,
75
123
  count,
76
124
  response,
@@ -86,6 +134,7 @@ export class ChatGPT3Broker<
86
134
  return doBreak()
87
135
  }
88
136
  }
137
+ await this.hook.notify('done', { id })
89
138
  throw error
90
139
  }
91
140
  })