Skip to content

Commit 1fc3cd8

Browse files
committed
update chatgpt web support (websocket mode) (#621, #627, #628, #630, #632, #634, #635, #637)
1 parent 98339aa commit 1fc3cd8

File tree

2 files changed

+130
-64
lines changed

2 files changed

+130
-64
lines changed

src/rules.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
{
5757
"operation": "set",
5858
"header": "referer",
59-
"value": "https://tcr9i.chat.openai.com/v2/2.4.0/enforcement.2e633b2c7bb736a0ee9965af3d9393cb.html"
59+
"value": "https://tcr9i.chat.openai.com/v2/2.4.1/enforcement.377e2ed937ca5b2169bf1fd2dae9bdf9.html"
6060
}
6161
]
6262
},

src/services/apis/chatgpt-web.mjs

Lines changed: 129 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,19 @@ export async function generateAnswersWithChatgptWebApi(port, question, session,
6161
session.parentMessageId = uuidv4()
6262
}
6363

64-
const { controller, messageListener, disconnectListener } = setAbortController(port, null, () => {
65-
if (session.autoClean) deleteConversation(accessToken, session.conversationId)
66-
})
64+
let ws
65+
const { controller, cleanController } = setAbortController(
66+
port,
67+
() => {
68+
if (ws) ws.close()
69+
},
70+
() => {
71+
if (session.autoClean) deleteConversation(accessToken, session.conversationId)
72+
if (ws) ws.close()
73+
},
74+
)
6775

68-
const models = await getModels(accessToken).catch(() => {
69-
port.onMessage.removeListener(messageListener)
70-
port.onDisconnect.removeListener(disconnectListener)
71-
})
76+
const models = await getModels(accessToken).catch(cleanController)
7277
console.debug('models', models)
7378
const config = await getUserConfig()
7479
const selectedModel = Models[session.modelName].value
@@ -116,7 +121,10 @@ export async function generateAnswersWithChatgptWebApi(port, question, session,
116121
let answer = ''
117122
let generationPrefixAnswer = ''
118123
let generatedImageUrl = ''
119-
await fetchSSE(`${config.customChatGptWebApiUrl}${config.customChatGptWebApiPath}`, {
124+
let wss_url = ''
125+
126+
const url = `${config.customChatGptWebApiUrl}${config.customChatGptWebApiPath}`
127+
const options = {
120128
method: 'POST',
121129
signal: controller.signal,
122130
credentials: 'include',
@@ -152,12 +160,115 @@ export async function generateAnswersWithChatgptWebApi(port, question, session,
152160
history_and_training_disabled: config.disableWebModeHistory,
153161
arkose_token: arkoseToken,
154162
}),
163+
}
164+
await fetchSSE(url, {
165+
...options,
155166
onMessage(message) {
156-
console.debug('sse message', message)
157-
if (message.trim() === '[DONE]') {
167+
function handleMessage(data) {
168+
if (data.error) {
169+
if (data.error.includes('unusual activity'))
170+
throw new Error(
171+
"Please keep https://chat.openai.com open and try again. If it still doesn't work, type some characters in the input box of chatgpt web page and try again.",
172+
)
173+
else throw new Error(data.error)
174+
}
175+
176+
if (data.conversation_id) session.conversationId = data.conversation_id
177+
if (data.message?.id) session.parentMessageId = data.message.id
178+
179+
const respAns = data.message?.content?.parts?.[0]
180+
const contentType = data.message?.content?.content_type
181+
if (contentType === 'text' && respAns) {
182+
answer =
183+
generationPrefixAnswer +
184+
(generatedImageUrl && `\n\n![](${generatedImageUrl})\n\n`) +
185+
respAns
186+
} else if (contentType === 'code' && data.message?.status === 'in_progress') {
187+
const generationText = '\n\n' + t('Generating...')
188+
if (answer && !answer.endsWith(generationText)) generationPrefixAnswer = answer
189+
answer = generationPrefixAnswer + generationText
190+
} else if (
191+
contentType === 'multimodal_text' &&
192+
respAns?.content_type === 'image_asset_pointer'
193+
) {
194+
const imageAsset = respAns?.asset_pointer || ''
195+
if (imageAsset) {
196+
fetch(
197+
`${config.customChatGptWebApiUrl}/backend-api/files/${imageAsset.replace(
198+
'file-service://',
199+
'',
200+
)}/download`,
201+
{
202+
credentials: 'include',
203+
headers: {
204+
Authorization: `Bearer ${accessToken}`,
205+
...(cookie && { Cookie: cookie }),
206+
},
207+
},
208+
).then((r) => r.json().then((json) => (generatedImageUrl = json?.download_url)))
209+
}
210+
}
211+
212+
if (answer) {
213+
port.postMessage({ answer: answer, done: false, session: null })
214+
}
215+
}
216+
217+
function finishMessage() {
158218
pushRecord(session, question, answer)
159219
console.debug('conversation history', { content: session.conversationRecords })
160-
port.postMessage({ answer: null, done: true, session: session })
220+
port.postMessage({ answer: answer, done: true, session: session })
221+
}
222+
223+
console.debug('sse message', message)
224+
if (message.trim() === '[DONE]') {
225+
if (!wss_url) {
226+
finishMessage()
227+
} else {
228+
ws = new WebSocket(wss_url)
229+
ws.onmessage = (event) => {
230+
let wsData
231+
try {
232+
wsData = JSON.parse(event.data)
233+
} catch (error) {
234+
console.debug('json error', error)
235+
return
236+
}
237+
if (wsData.type === 'http.response.body') {
238+
let body
239+
try {
240+
body = atob(wsData.body).replace(/^data:/, '')
241+
const data = JSON.parse(body)
242+
console.debug('ws message', data)
243+
if (wsData.conversation_id === session.conversationId) {
244+
handleMessage(data)
245+
}
246+
} catch (error) {
247+
if (body && body.trim() === '[DONE]') {
248+
console.debug('ws message', '[DONE]')
249+
if (wsData.conversation_id === session.conversationId) {
250+
finishMessage()
251+
ws.close()
252+
}
253+
} else {
254+
console.debug('json error', error)
255+
}
256+
}
257+
}
258+
}
259+
ws.onopen = () => {
260+
// fetch(url, options)
261+
}
262+
ws.onclose = () => {
263+
port.postMessage({ done: true })
264+
cleanController()
265+
}
266+
ws.onerror = (event) => {
267+
console.debug('ws error', event)
268+
port.postMessage({ error: event })
269+
cleanController()
270+
}
271+
}
161272
return
162273
}
163274
let data
@@ -167,65 +278,20 @@ export async function generateAnswersWithChatgptWebApi(port, question, session,
167278
console.debug('json error', error)
168279
return
169280
}
170-
if (data.error) {
171-
if (data.error.includes('unusual activity'))
172-
throw new Error(
173-
"Please keep https://chat.openai.com open and try again. If it still doesn't work, type some characters in the input box of chatgpt web page and try again.",
174-
)
175-
else throw new Error(data.error)
176-
}
177-
178-
if (data.conversation_id) session.conversationId = data.conversation_id
179-
if (data.message?.id) session.parentMessageId = data.message.id
180-
181-
const respAns = data.message?.content?.parts?.[0]
182-
const contentType = data.message?.content?.content_type
183-
if (contentType === 'text' && respAns) {
184-
answer =
185-
generationPrefixAnswer +
186-
(generatedImageUrl && `\n\n![](${generatedImageUrl})\n\n`) +
187-
respAns
188-
} else if (contentType === 'code' && data.message?.status === 'in_progress') {
189-
const generationText = '\n\n' + t('Generating...')
190-
if (answer && !answer.endsWith(generationText)) generationPrefixAnswer = answer
191-
answer = generationPrefixAnswer + generationText
192-
} else if (
193-
contentType === 'multimodal_text' &&
194-
respAns?.content_type === 'image_asset_pointer'
195-
) {
196-
const imageAsset = respAns?.asset_pointer || ''
197-
if (imageAsset) {
198-
fetch(
199-
`${config.customChatGptWebApiUrl}/backend-api/files/${imageAsset.replace(
200-
'file-service://',
201-
'',
202-
)}/download`,
203-
{
204-
credentials: 'include',
205-
headers: {
206-
Authorization: `Bearer ${accessToken}`,
207-
...(cookie && { Cookie: cookie }),
208-
},
209-
},
210-
).then((r) => r.json().then((json) => (generatedImageUrl = json?.download_url)))
211-
}
212-
}
213-
214-
if (answer) {
215-
port.postMessage({ answer: answer, done: false, session: null })
216-
}
281+
if (data.wss_url) wss_url = data.wss_url
282+
handleMessage(data)
217283
},
218284
async onStart() {
219285
// sendModerations(accessToken, question, session.conversationId, session.messageId)
220286
},
221287
async onEnd() {
222-
port.postMessage({ done: true })
223-
port.onMessage.removeListener(messageListener)
224-
port.onDisconnect.removeListener(disconnectListener)
288+
if (!wss_url) {
289+
port.postMessage({ done: true })
290+
cleanController()
291+
}
225292
},
226293
async onError(resp) {
227-
port.onMessage.removeListener(messageListener)
228-
port.onDisconnect.removeListener(disconnectListener)
294+
cleanController()
229295
if (resp instanceof Error) throw resp
230296
if (resp.status === 403) {
231297
throw new Error('CLOUDFLARE')

0 commit comments

Comments
 (0)