Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
C
Chatgpt Puppeteer
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nanahira
Chatgpt Puppeteer
Commits
e0fd5f46
Commit
e0fd5f46
authored
Jan 12, 2023
by
Travis Fischer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add onProgress to ChatGPTAPIBrowser.sendMessage
parent
525524b8
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
142 additions
and
14 deletions
+142
-14
demos/demo-on-progress.ts
demos/demo-on-progress.ts
+47
-0
readme.md
readme.md
+13
-1
src/chatgpt-api-browser.ts
src/chatgpt-api-browser.ts
+40
-3
src/openai-auth.ts
src/openai-auth.ts
+10
-4
src/utils.ts
src/utils.ts
+32
-6
No files found.
demos/demo-on-progress.ts
0 → 100644
View file @
e0fd5f46
import
dotenv
from
'
dotenv-safe
'
import
{
oraPromise
}
from
'
ora
'
import
{
ChatGPTAPIBrowser
}
from
'
../src
'
dotenv
.
config
()
/**
* Demo CLI for testing the `onProgress` handler.
*
* ```
* npx tsx demos/demo-on-progress.ts
* ```
*/
async
function
main
()
{
const
email
=
process
.
env
.
OPENAI_EMAIL
const
password
=
process
.
env
.
OPENAI_PASSWORD
const
api
=
new
ChatGPTAPIBrowser
({
email
,
password
,
debug
:
false
,
minimize
:
true
})
await
api
.
initSession
()
const
prompt
=
'
Write a python version of bubble sort. Do not include example usage.
'
console
.
log
(
prompt
)
const
res
=
await
api
.
sendMessage
(
prompt
,
{
onProgress
:
(
partialResponse
)
=>
{
console
.
log
(
'
p
'
)
console
.
log
(
'
progress
'
,
partialResponse
?.
response
)
}
})
console
.
log
(
res
.
response
)
// close the browser at the end
await
api
.
closeSession
()
}
main
().
catch
((
err
)
=>
{
console
.
error
(
err
)
process
.
exit
(
1
)
})
readme.md
View file @
e0fd5f46
...
@@ -197,7 +197,19 @@ A [basic demo](./demos/demo.ts) is included for testing purposes:
...
@@ -197,7 +197,19 @@ A [basic demo](./demos/demo.ts) is included for testing purposes:
npx tsx demos/demo.ts
npx tsx demos/demo.ts
```
```
A
[
conversation demo
](
./demos/demo-conversation.ts
)
is also included:
A
[
google auth demo
](
./demos/demo-google-auth.ts
)
:
```
bash
npx tsx demos/demo-google-auth.ts
```
A
[
demo showing on progress handler
](
./demos/demo-on-progress.ts
)
:
```
bash
npx tsx demos/demo-on-progress.ts
```
A
[
conversation demo
](
./demos/demo-conversation.ts
)
:
```
bash
```
bash
npx tsx demos/demo-conversation.ts
npx tsx demos/demo-conversation.ts
...
...
src/chatgpt-api-browser.ts
View file @
e0fd5f46
...
@@ -33,6 +33,10 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
...
@@ -33,6 +33,10 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
protected
_page
:
Page
protected
_page
:
Page
protected
_proxyServer
:
string
protected
_proxyServer
:
string
protected
_isRefreshing
:
boolean
protected
_isRefreshing
:
boolean
protected
_messageOnProgressHandlers
:
Record
<
string
,
(
partialResponse
:
types
.
ChatResponse
)
=>
void
>
/**
/**
* Creates a new client for automating the ChatGPT webapp.
* Creates a new client for automating the ChatGPT webapp.
...
@@ -97,6 +101,7 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
...
@@ -97,6 +101,7 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
this
.
_executablePath
=
executablePath
this
.
_executablePath
=
executablePath
this
.
_proxyServer
=
proxyServer
this
.
_proxyServer
=
proxyServer
this
.
_isRefreshing
=
false
this
.
_isRefreshing
=
false
this
.
_messageOnProgressHandlers
=
{}
if
(
!
this
.
_email
)
{
if
(
!
this
.
_email
)
{
const
error
=
new
types
.
ChatGPTError
(
'
ChatGPT invalid email
'
)
const
error
=
new
types
.
ChatGPTError
(
'
ChatGPT invalid email
'
)
...
@@ -196,6 +201,24 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
...
@@ -196,6 +201,24 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
})
})
}
}
// TODO: will this exist after page reload and navigation?
await
this
.
_page
.
exposeFunction
(
'
ChatGPTAPIBrowserOnProgress
'
,
(
partialResponse
:
types
.
ChatResponse
)
=>
{
if
((
partialResponse
as
any
)?.
origMessageId
)
{
const
onProgress
=
this
.
_messageOnProgressHandlers
[
(
partialResponse
as
any
).
origMessageId
]
if
(
onProgress
)
{
onProgress
(
partialResponse
)
return
}
}
}
)
// dismiss welcome modal (and other modals)
// dismiss welcome modal (and other modals)
do
{
do
{
const
modalSelector
=
'
[data-headlessui-state="open"]
'
const
modalSelector
=
'
[data-headlessui-state="open"]
'
...
@@ -482,9 +505,8 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
...
@@ -482,9 +505,8 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
parentMessageId
=
uuidv4
(),
parentMessageId
=
uuidv4
(),
messageId
=
uuidv4
(),
messageId
=
uuidv4
(),
action
=
'
next
'
,
action
=
'
next
'
,
timeoutMs
timeoutMs
,
// TODO
onProgress
// onProgress
}
=
opts
}
=
opts
const
url
=
`https://chat.openai.com/backend-api/conversation`
const
url
=
`https://chat.openai.com/backend-api/conversation`
...
@@ -508,6 +530,16 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
...
@@ -508,6 +530,16 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
body
.
conversation_id
=
conversationId
body
.
conversation_id
=
conversationId
}
}
if
(
onProgress
)
{
this
.
_messageOnProgressHandlers
[
messageId
]
=
onProgress
}
const
cleanup
=
()
=>
{
if
(
this
.
_messageOnProgressHandlers
[
messageId
])
{
delete
this
.
_messageOnProgressHandlers
[
messageId
]
}
}
let
result
:
types
.
ChatResponse
|
types
.
ChatError
let
result
:
types
.
ChatResponse
|
types
.
ChatError
let
numTries
=
0
let
numTries
=
0
let
is401
=
false
let
is401
=
false
...
@@ -528,6 +560,7 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
...
@@ -528,6 +560,7 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
if
(
!
(
await
this
.
getIsAuthenticated
()))
{
if
(
!
(
await
this
.
getIsAuthenticated
()))
{
const
error
=
new
types
.
ChatGPTError
(
'
Not signed in
'
)
const
error
=
new
types
.
ChatGPTError
(
'
Not signed in
'
)
error
.
statusCode
=
401
error
.
statusCode
=
401
cleanup
()
throw
error
throw
error
}
}
}
}
...
@@ -551,6 +584,7 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
...
@@ -551,6 +584,7 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
const
error
=
new
types
.
ChatGPTError
(
err
.
toString
())
const
error
=
new
types
.
ChatGPTError
(
err
.
toString
())
error
.
statusCode
=
err
.
response
?.
statusCode
error
.
statusCode
=
err
.
response
?.
statusCode
error
.
statusText
=
err
.
response
?.
statusText
error
.
statusText
=
err
.
response
?.
statusText
cleanup
()
throw
error
throw
error
}
}
...
@@ -570,6 +604,7 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
...
@@ -570,6 +604,7 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
is401
=
true
is401
=
true
if
(
numTries
>=
2
)
{
if
(
numTries
>=
2
)
{
cleanup
()
throw
error
throw
error
}
else
{
}
else
{
continue
continue
...
@@ -590,10 +625,12 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
...
@@ -590,10 +625,12 @@ export class ChatGPTAPIBrowser extends AChatGPTAPI {
result
.
response
=
markdownToText
(
result
.
response
)
result
.
response
=
markdownToText
(
result
.
response
)
}
}
cleanup
()
return
result
return
result
}
}
}
while
(
!
result
)
}
while
(
!
result
)
cleanup
()
// console.log('<<< EVALUATE', result)
// console.log('<<< EVALUATE', result)
// const lastMessage = await this.getLastMessage()
// const lastMessage = await this.getLastMessage()
...
...
src/openai-auth.ts
View file @
e0fd5f46
...
@@ -272,6 +272,7 @@ export async function getBrowser(
...
@@ -272,6 +272,7 @@ export async function getBrowser(
nopechaKey
?:
string
nopechaKey
?:
string
proxyServer
?:
string
proxyServer
?:
string
minimize
?:
boolean
minimize
?:
boolean
debug
?:
boolean
timeoutMs
?:
number
timeoutMs
?:
number
}
=
{}
}
=
{}
)
{
)
{
...
@@ -281,6 +282,7 @@ export async function getBrowser(
...
@@ -281,6 +282,7 @@ export async function getBrowser(
executablePath
=
defaultChromeExecutablePath
(),
executablePath
=
defaultChromeExecutablePath
(),
proxyServer
=
process
.
env
.
PROXY_SERVER
,
proxyServer
=
process
.
env
.
PROXY_SERVER
,
minimize
=
false
,
minimize
=
false
,
debug
=
false
,
timeoutMs
=
DEFAULT_TIMEOUT_MS
,
timeoutMs
=
DEFAULT_TIMEOUT_MS
,
...
launchOptions
...
launchOptions
}
=
opts
}
=
opts
...
@@ -387,8 +389,9 @@ export async function getBrowser(
...
@@ -387,8 +389,9 @@ export async function getBrowser(
}
}
await
initializeNopechaExtension
(
browser
,
{
await
initializeNopechaExtension
(
browser
,
{
minimize
,
nopechaKey
,
nopechaKey
,
minimize
,
debug
,
timeoutMs
timeoutMs
})
})
...
@@ -398,12 +401,13 @@ export async function getBrowser(
...
@@ -398,12 +401,13 @@ export async function getBrowser(
export
async
function
initializeNopechaExtension
(
export
async
function
initializeNopechaExtension
(
browser
:
Browser
,
browser
:
Browser
,
opts
:
{
opts
:
{
minimize
?:
boolean
nopechaKey
?:
string
nopechaKey
?:
string
minimize
?:
boolean
debug
?:
boolean
timeoutMs
?:
number
timeoutMs
?:
number
}
}
)
{
)
{
const
{
minimize
=
false
,
nopechaKey
}
=
opts
const
{
minimize
=
false
,
debug
=
false
,
nopechaKey
}
=
opts
if
(
hasNopechaExtension
)
{
if
(
hasNopechaExtension
)
{
const
page
=
(
await
browser
.
pages
())[
0
]
||
(
await
browser
.
newPage
())
const
page
=
(
await
browser
.
pages
())[
0
]
||
(
await
browser
.
newPage
())
...
@@ -411,7 +415,9 @@ export async function initializeNopechaExtension(
...
@@ -411,7 +415,9 @@ export async function initializeNopechaExtension(
await
minimizePage
(
page
)
await
minimizePage
(
page
)
}
}
if
(
debug
)
{
console
.
log
(
'
initializing nopecha extension with key
'
,
nopechaKey
,
'
...
'
)
console
.
log
(
'
initializing nopecha extension with key
'
,
nopechaKey
,
'
...
'
)
}
// TODO: setting the nopecha extension key is really, really error prone...
// TODO: setting the nopecha extension key is really, really error prone...
for
(
let
i
=
0
;
i
<
5
;
++
i
)
{
for
(
let
i
=
0
;
i
<
5
;
++
i
)
{
...
...
src/utils.ts
View file @
e0fd5f46
...
@@ -9,6 +9,12 @@ import stripMarkdown from 'strip-markdown'
...
@@ -9,6 +9,12 @@ import stripMarkdown from 'strip-markdown'
import
*
as
types
from
'
./types
'
import
*
as
types
from
'
./types
'
declare
global
{
function
ChatGPTAPIBrowserOnProgress
(
partialChatResponse
:
types
.
ChatResponse
):
Promise
<
void
>
}
export
function
markdownToText
(
markdown
?:
string
):
string
{
export
function
markdownToText
(
markdown
?:
string
):
string
{
return
remark
()
return
remark
()
.
use
(
stripMarkdown
)
.
use
(
stripMarkdown
)
...
@@ -103,6 +109,7 @@ export async function browserPostEventStream(
...
@@ -103,6 +109,7 @@ export async function browserPostEventStream(
const
BOM
=
[
239
,
187
,
191
]
const
BOM
=
[
239
,
187
,
191
]
let
conversationId
:
string
=
body
?.
conversation_id
let
conversationId
:
string
=
body
?.
conversation_id
const
origMessageId
=
body
?.
messages
?.[
0
]?.
id
let
messageId
:
string
=
body
?.
messages
?.[
0
]?.
id
let
messageId
:
string
=
body
?.
messages
?.[
0
]?.
id
let
response
=
''
let
response
=
''
...
@@ -142,7 +149,7 @@ export async function browserPostEventStream(
...
@@ -142,7 +149,7 @@ export async function browserPostEventStream(
const
responseP
=
new
Promise
<
types
.
ChatResponse
>
(
const
responseP
=
new
Promise
<
types
.
ChatResponse
>
(
async
(
resolve
,
reject
)
=>
{
async
(
resolve
,
reject
)
=>
{
function
onMessage
(
data
:
string
)
{
async
function
onMessage
(
data
:
string
)
{
if
(
data
===
'
[DONE]
'
)
{
if
(
data
===
'
[DONE]
'
)
{
return
resolve
({
return
resolve
({
response
,
response
,
...
@@ -150,16 +157,24 @@ export async function browserPostEventStream(
...
@@ -150,16 +157,24 @@ export async function browserPostEventStream(
messageId
messageId
})
})
}
}
let
convoResponseEvent
:
types
.
ConversationResponseEvent
try
{
try
{
const
checkJson
=
JSON
.
parse
(
data
)
convoResponseEvent
=
JSON
.
parse
(
data
)
}
catch
(
error
)
{
}
catch
(
err
)
{
console
.
log
(
'
warning: parse error.
'
)
console
.
warn
(
'
warning: chatgpt even stream parse error
'
,
err
.
toString
(),
data
)
return
}
if
(
!
convoResponseEvent
)
{
return
return
}
}
try
{
try
{
const
convoResponseEvent
:
types
.
ConversationResponseEvent
=
JSON
.
parse
(
data
)
if
(
convoResponseEvent
.
conversation_id
)
{
if
(
convoResponseEvent
.
conversation_id
)
{
conversationId
=
convoResponseEvent
.
conversation_id
conversationId
=
convoResponseEvent
.
conversation_id
}
}
...
@@ -172,6 +187,17 @@ export async function browserPostEventStream(
...
@@ -172,6 +187,17 @@ export async function browserPostEventStream(
convoResponseEvent
.
message
?.
content
?.
parts
?.[
0
]
convoResponseEvent
.
message
?.
content
?.
parts
?.[
0
]
if
(
partialResponse
)
{
if
(
partialResponse
)
{
response
=
partialResponse
response
=
partialResponse
if
(
window
.
ChatGPTAPIBrowserOnProgress
)
{
const
partialChatResponse
=
{
origMessageId
,
response
,
conversationId
,
messageId
}
await
window
.
ChatGPTAPIBrowserOnProgress
(
partialChatResponse
)
}
}
}
}
catch
(
err
)
{
}
catch
(
err
)
{
console
.
warn
(
'
fetchSSE onMessage unexpected error
'
,
err
)
console
.
warn
(
'
fetchSSE onMessage unexpected error
'
,
err
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment