Repository navigation
Expand file tree
/
Copy pathstep.ts
More file actions
324 lines (276 loc) · 8.69 KB
/
Copy pathstep.ts
File metadata and controls
324 lines (276 loc) · 8.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
import type { Page, PuppeteerLifeCycleEvent } from 'puppeteer'
import { TimeoutError } from 'puppeteer'
import type { StartResearchContext, StartResearchOptions } from './type'
import { input } from './utils'
import fs from 'fs'
import {
checkDownloadError,
getPageContent,
getTabbableElements,
waitForNavigation
} from './browser'
import { printCurrentCost } from './token'
import { sendChatMessage } from './chat'
export async function doNextStep({
page,
context,
aiContext,
next_step,
links_and_inputs,
element,
options
}: {
page: Page
aiContext
next_step
links_and_inputs
element
options: StartResearchOptions
context: StartResearchContext
}) {
let message
let msg
let no_content = false
if (next_step.hasOwnProperty('function_call')) {
let function_call = next_step.function_call
let function_name = function_call.name
let func_arguments
try {
func_arguments = JSON.parse(function_call.arguments)
} catch (e) {
if (function_name === 'answer_user') {
func_arguments = {
answer: function_call.arguments
}
}
}
if (function_name === 'make_plan') {
message = 'OK. Please continue according to the plan'
} else if (function_name === 'read_file') {
let filename = func_arguments.filename
if (
options.autopilot ||
(await input(
'\nGPT: I want to read the file ' +
filename +
'\nDo you allow this? (y/n): '
)) == 'y'
) {
console.log()
console.log(options.taskPrefix + 'Reading file ' + filename)
if (fs.existsSync(filename)) {
let file_data = fs.readFileSync(filename, 'utf-8')
file_data = file_data.substring(0, options.contextLengthLimit)
message = file_data
} else {
message = 'ERROR: That file does not exist'
}
} else {
console.log()
message = 'ERROR: You are not allowed to read this file'
}
} else if (function_name === 'goto_url') {
let url = func_arguments.url
console.log(options.taskPrefix + 'Going to ' + url)
try {
await page.goto(url, {
waitUntil: options.waitUntil as PuppeteerLifeCycleEvent
})
url = await page.url()
message = `You are now on ${url}`
} catch (error) {
message = checkDownloadError({ error, options })
message = message ?? 'There was an error going to the URL'
}
console.log(options.taskPrefix + 'Scraping page...')
links_and_inputs = await getTabbableElements({
page,
options
})
} else if (function_name === 'click_link') {
let link_id = func_arguments.pgpt_id
let link_text = func_arguments.text
if (!link_id) {
message = 'ERROR: Missing parameter pgpt_id'
} else if (!link_text) {
message = ''
aiContext.pop()
msg = {
role: 'user',
content:
'Please the correct link on the page. Remember to set both the text and the pgpt_id parameter.'
}
} else {
const link = links_and_inputs.find((elem) => elem && elem.id == link_id)
try {
console.log(options.taskPrefix + `Clicking link "${link.text}"`)
context.requestCount = 0
context.responseCount = 0
context.downloaStarted = false
if (!page.$('.pgpt-element' + link_id)) {
throw new Error('Element not found')
}
page.click('.pgpt-element' + link_id)
await waitForNavigation({ page, options })
let url = await page.url()
if (context.downloaStarted) {
context.downloaStarted = false
message = 'Link clicked and file download started successfully!'
no_content = true
} else {
message = 'Link clicked! You are now on ' + url
}
} catch (error) {
if (options.debug) {
console.log(error)
}
if (error instanceof TimeoutError) {
message = 'NOTICE: The click did not cause a navigation.'
} else {
let link_text = link ? link.text : ''
message = `Sorry, but link number ${link_id} (${link_text}) is not clickable, please select another link or another command. You can also try to go to the link URL directly with "goto_url".`
}
}
}
console.log(options.taskPrefix + 'Scraping page...')
links_and_inputs = await getTabbableElements({
page,
options
})
} else if (function_name === 'type_text') {
let form_data = func_arguments.form_data
let prev_input
for (let data of form_data) {
let element_id = data.pgpt_id
let text = data.text
message = ''
try {
element = await page.$('.pgpt-element' + element_id)
if (!prev_input) {
prev_input = element
}
const name = await element.evaluate((el) => {
return el.getAttribute('name')
})
const type = await element.evaluate((el) => {
return el.getAttribute('type')
})
const tagName = await element.evaluate((el) => {
return el.tagName
})
// ChatGPT sometimes tries to type empty string
// to buttons to click them
if (tagName === 'BUTTON' || type === 'submit' || type === 'button') {
func_arguments.submit = true
} else {
prev_input = element
await element.type(text)
let sanitized = text.replace('\n', ' ')
console.log(options.taskPrefix + `Typing "${sanitized}" to ${name}`)
message += `Typed "${text}" to input field "${name}"\n`
}
} catch (error) {
if (options.debug) {
console.log(error)
}
message += `Error typing "${text}" to input field ID ${data.element_id}\n`
}
}
if (func_arguments.submit !== false) {
console.log(options.taskPrefix + `Submitting form`)
try {
const form = await prev_input.evaluateHandle((input) =>
input.closest('form')
)
await form.evaluate((form) => form.submit())
await waitForNavigation({ page, options })
let url = await page.url()
message += `Form sent! You are now on ${url}\n`
} catch (error) {
if (options.debug) {
console.log(error)
}
console.log(options.taskPrefix + `Error submitting form`)
message += 'There was an error submitting the form.\n'
}
console.log(options.taskPrefix + 'Scraping page...')
links_and_inputs = await getTabbableElements({ page, options })
}
} else if (function_name === 'answer_user') {
let text = func_arguments.answer
if (!text) {
text = func_arguments.summary
}
printCurrentCost({
token_usage: context.tokenUsage,
model: options.model,
logger: options.logger
})
if (options.autopilot) {
message = await input('<!_RESPONSE_!>' + JSON.stringify(text) + '\n')
} else {
message = await input('\nGPT: ' + text + '\nYou: ')
}
console.log()
} else {
message = 'That is an unknown function. Please call another one'
}
message = message.substring(0, options.contextLengthLimit)
msg = msg ?? {
role: 'function',
name: function_name,
content: JSON.stringify({
status: 'OK',
message: message
})
}
} else {
printCurrentCost({
token_usage: context.tokenUsage,
model: options.model,
logger: options.logger
})
let next_content = next_step.content.trim()
if (next_content === '') {
next_content = '<empty response>'
}
if (options.autopilot) {
message = await input(
'<!_RESPONSE_!>' + JSON.stringify(next_content) + '\n'
)
} else {
message = await input('GPT: ' + next_content + '\nYou: ')
console.log()
}
msg = {
role: 'user',
content: message
}
}
if (no_content !== true) {
const page_content = await getPageContent(page)
msg.content +=
'\n\n' + page_content.substring(0, options.contextLengthLimit)
}
msg.url = await page.url()
next_step = await sendChatMessage({
message: msg,
aiContext,
options,
context
})
;(msg.content = message), aiContext.push(msg)
aiContext.push(next_step)
if (options.debug) {
fs.writeFileSync('context.json', JSON.stringify(aiContext, null, 2))
}
await doNextStep({
page,
aiContext,
next_step,
links_and_inputs,
element,
options,
context
})
}