返回提交历史
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+45
-43
Modified
g4f/gui/client/css/style.css
+16
-5
Modified
g4f/gui/client/html/index.html
+1
-0
Modified
g4f/gui/client/js/chat.v1.js
+72
-55
XFEstudio/gpt4free
Add system message input to gui Improve OpenaiChat provider
14167671
代码差异
4 个文件
+134
-103
@@ -10,11 +10,10 @@ from aiohttp import ClientWebSocketResponse
10
10
11
11
try:
12
12
from py_arkose_generator.arkose import get_values_for_request
13
from async_property import async_cached_property
14
has_requirements = True
13
has_arkose_generator = True
15
14
except ImportError:
16
async_cached_property = property
17
has_requirements = False
15
has_arkose_generator = False
16
18
17
try:
19
18
from selenium.webdriver.common.by import By
20
19
from selenium.webdriver.support.ui import WebDriverWait
@@ -34,7 +33,7 @@ from ... import debug
34
33
35
34
class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
36
35
"""A class for creating and managing conversations with OpenAI chat service"""
37
36
38
37
url = "https://chat.openai.com"
39
38
working = True
40
39
needs_auth = True
@@ -81,7 +80,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
81
80
A Response object that contains the generator, action, messages, and options
82
81
"""
83
82
# Add the user input to the messages list
84
if prompt:
83
if prompt is not None:
85
84
messages.append({
86
85
"role": "user",
87
86
"content": prompt
@@ -103,7 +102,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
103
102
messages,
104
103
kwargs
105
104
)
106
105
107
106
@classmethod
108
107
async def upload_image(
109
108
cls,
@@ -163,7 +162,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
163
162
response.raise_for_status()
164
163
image_data["download_url"] = (await response.json())["download_url"]
165
164
return ImageRequest(image_data)
166
165
167
166
@classmethod
168
167
async def get_default_model(cls, session: StreamSession, headers: dict):
169
168
"""
@@ -186,7 +185,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
186
185
return cls.default_model
187
186
raise RuntimeError(f"Response: {data}")
188
187
return cls.default_model
189
188
190
189
@classmethod
191
190
def create_messages(cls, messages: Messages, image_request: ImageRequest = None):
192
191
"""
@@ -335,9 +334,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
335
334
Raises:
336
335
RuntimeError: If an error occurs during processing.
337
336
"""
338
if not has_requirements:
339
raise MissingRequirementsError('Install "py-arkose-generator" and "async_property" package')
340
if not parent_id:
337
if parent_id is None:
341
338
parent_id = str(uuid.uuid4())
342
339
343
340
# Read api_key from arguments
@@ -349,7 +346,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
349
346
timeout=timeout
350
347
) as session:
351
348
# Read api_key and cookies from cache / browser config
352
if cls._headers is None or time.time() > cls._expires:
349
if cls._headers is None or cls._expires is None or time.time() > cls._expires:
353
350
if api_key is None:
354
351
# Read api_key from cookies
355
352
cookies = get_cookies("chat.openai.com", False) if cookies is None else cookies
@@ -358,8 +355,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
358
355
else:
359
356
api_key = cls._api_key if api_key is None else api_key
360
357
# Read api_key with session cookies
361
if api_key is None and cookies:
362
api_key = await cls.fetch_access_token(session, cls._headers)
358
#if api_key is None and cookies:
359
# api_key = await cls.fetch_access_token(session, cls._headers)
363
360
# Load default model
364
361
if cls.default_model is None and api_key is not None:
365
362
try:
@@ -385,6 +382,19 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
385
382
else:
386
383
cls._set_api_key(api_key)
387
384
385
async with session.post(
386
f"{cls.url}/backend-api/sentinel/chat-requirements",
387
json={"conversation_mode_kind": "primary_assistant"},
388
headers=cls._headers
389
) as response:
390
response.raise_for_status()
391
data = await response.json()
392
need_arkose = data["arkose"]["required"]
393
chat_token = data["token"]
394
395
if need_arkose and not has_arkose_generator:
396
raise MissingRequirementsError('Install "py-arkose-generator" package')
397
388
398
try:
389
399
image_request = await cls.upload_image(session, cls._headers, image, image_name) if image else None
390
400
except Exception as e:
@@ -395,12 +405,10 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
395
405
model = cls.get_model(model).replace("gpt-3.5-turbo", "text-davinci-002-render-sha")
396
406
fields = ResponseFields()
397
407
while fields.finish_reason is None:
398
arkose_token = await cls.get_arkose_token(session)
399
408
conversation_id = conversation_id if fields.conversation_id is None else fields.conversation_id
400
409
parent_id = parent_id if fields.message_id is None else fields.message_id
401
410
data = {
402
411
"action": action,
403
"arkose_token": arkose_token,
404
412
"conversation_mode": {"kind": "primary_assistant"},
405
413
"force_paragen": False,
406
414
"force_rate_limit": False,
@@ -418,7 +426,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
418
426
json=data,
419
427
headers={
420
428
"Accept": "text/event-stream",
421
"OpenAI-Sentinel-Arkose-Token": arkose_token,
429
**({"OpenAI-Sentinel-Arkose-Token": await cls.get_arkose_token(session)} if need_arkose else {}),
430
"OpenAI-Sentinel-Chat-Requirements-Token": chat_token,
422
431
**cls._headers
423
432
}
424
433
) as response:
@@ -471,6 +480,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
471
480
if not line.startswith(b"data: "):
472
481
return
473
482
elif line.startswith(b"data: [DONE]"):
483
if fields.finish_reason is None:
484
fields.finish_reason = "error"
474
485
return
475
486
try:
476
487
line = json.loads(line[6:])
@@ -600,16 +611,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
600
611
def _update_cookie_header(cls):
601
612
cls._headers["Cookie"] = cls._format_cookies(cls._cookies)
602
613
603
class EndTurn:
604
"""
605
Class to represent the end of a conversation turn.
606
"""
607
def __init__(self):
608
self.is_end = False
609
610
def end(self):
611
self.is_end = True
612
613
614
class ResponseFields:
614
615
"""
615
616
Class to encapsulate response fields.
@@ -638,8 +639,8 @@ class Response():
638
639
self._options = options
639
640
self._fields = None
640
641
641
async def generator(self):
642
if self._generator:
642
async def generator(self) -> AsyncIterator:
643
if self._generator is not None:
643
644
self._generator = None
644
645
chunks = []
645
646
async for chunk in self._generator:
@@ -649,27 +650,29 @@ class Response():
649
650
yield chunk
650
651
chunks.append(str(chunk))
651
652
self._message = "".join(chunks)
652
if not self._fields:
653
if self._fields is None:
653
654
raise RuntimeError("Missing response fields")
654
self.is_end = self._fields.end_turn
655
self.is_end = self._fields.finish_reason == "stop"
655
656
656
657
def __aiter__(self):
657
658
return self.generator()
658
659
659
@async_cached_property
660
async def message(self) -> str:
660
async def get_message(self) -> str:
661
661
await self.generator()
662
662
return self._message
663
663
664
async def get_fields(self):
664
async def get_fields(self) -> dict:
665
665
await self.generator()
666
return {"conversation_id": self._fields.conversation_id, "parent_id": self._fields.message_id}
666
return {
667
"conversation_id": self._fields.conversation_id,
668
"parent_id": self._fields.message_id
669
}
667
670
668
async def next(self, prompt: str, **kwargs) -> Response:
671
async def create_next(self, prompt: str, **kwargs) -> Response:
669
672
return await OpenaiChat.create(
670
673
**self._options,
671
674
prompt=prompt,
672
messages=await self.messages,
675
messages=await self.get_messages(),
673
676
action="next",
674
677
**await self.get_fields(),
675
678
**kwargs
@@ -681,13 +684,13 @@ class Response():
681
684
raise RuntimeError("Can't continue message. Message already finished.")
682
685
return await OpenaiChat.create(
683
686
**self._options,
684
messages=await self.messages,
687
messages=await self.get_messages(),
685
688
action="continue",
686
689
**fields,
687
690
**kwargs
688
691
)
689
692
690
async def variant(self, **kwargs) -> Response:
693
async def create_variant(self, **kwargs) -> Response:
691
694
if self.action != "next":
692
695
raise RuntimeError("Can't create variant from continue or variant request.")
693
696
return await OpenaiChat.create(
@@ -698,8 +701,7 @@ class Response():
698
701
**kwargs
699
702
)
700
703
701
@async_cached_property
702
async def messages(self):
704
async def get_messages(self) -> list:
703
705
messages = self._messages
704
messages.append({"role": "assistant", "content": await self.message})
706
messages.append({"role": "assistant", "content": await self.message()})
705
707
return messages
@@ -65,6 +65,7 @@
65
65
:root {
66
66
--font-1: "Inter", sans-serif;
67
67
--section-gap: 25px;
68
--inner-gap: 15px;
68
69
--border-radius-1: 8px;
69
70
}
70
71
@@ -222,7 +223,7 @@ body {
222
223
overflow-wrap: break-word;
223
224
display: flex;
224
225
gap: var(--section-gap);
225
padding: var(--section-gap);
226
padding: var(--inner-gap) var(--section-gap);
226
227
padding-bottom: 0;
227
228
}
228
229
@@ -393,7 +394,7 @@ body {
393
394
#input-count {
394
395
width: fit-content;
395
396
font-size: 12px;
396
padding: 6px 15px;
397
padding: 6px var(--inner-gap);
397
398
}
398
399
399
400
.stop_generating, .regenerate {
@@ -417,7 +418,7 @@ body {
417
418
background-color: var(--blur-bg);
418
419
border-radius: var(--border-radius-1);
419
420
border: 1px solid var(--blur-border);
420
padding: 5px 15px;
421
padding: 5px var(--inner-gap);
421
422
color: var(--colour-3);
422
423
display: flex;
423
424
justify-content: center;
@@ -601,7 +602,7 @@ select {
601
602
.input-box {
602
603
display: flex;
603
604
align-items: center;
604
padding-right: 15px;
605
padding-right: var(--inner-gap);
605
606
cursor: pointer;
606
607
}
607
608
@@ -785,7 +786,7 @@ a:-webkit-any-link {
785
786
font-size: 15px;
786
787
width: 100%;
787
788
height: 100%;
788
padding: 12px 15px;
789
padding: 12px var(--inner-gap);
789
790
background: none;
790
791
border: none;
791
792
outline: none;
@@ -997,3 +998,13 @@ a:-webkit-any-link {
997
998
#send-button:hover {
998
999
border: 1px solid #e4d4ffc9;
999
1000
}
1001
1002
#systemPrompt {
1003
font-size: 15px;
1004
width: 100%;
1005
color: var(--colour-3);
1006
height: 50px;
1007
outline: none;
1008
padding: var(--inner-gap) var(--section-gap);
1009
resize: vertical;
1010
}
@@ -116,6 +116,7 @@
116
116
</div>
117
117
</div>
118
118
<div class="conversation">
119
<textarea id="systemPrompt" class="box" placeholder="System prompt"></textarea>
119
120
<div id="messages" class="box"></div>
120
121
<div class="toolbar">
121
122
<div id="input-count" class="">
@@ -13,6 +13,7 @@ const cameraInput = document.getElementById("camera");
13
13
const fileInput = document.getElementById("file");
14
14
const inputCount = document.getElementById("input-count")
15
15
const modelSelect = document.getElementById("model");
16
const systemPrompt = document.getElementById("systemPrompt")
16
17
17
18
let prompt_lock = false;
18
19
@@ -135,7 +136,7 @@ const remove_cancel_button = async () => {
135
136
}, 300);
136
137
};
137
138
138
const filter_messages = (messages, filter_last_message = true) => {
139
const prepare_messages = (messages, filter_last_message = true) => {
139
140
// Removes none user messages at end
140
141
if (filter_last_message) {
141
142
let last_message;
@@ -147,7 +148,7 @@ const filter_messages = (messages, filter_last_message = true) => {
147
148
}
148
149
}
149
150
150
// Remove history, if it is selected
151
// Remove history, if it's selected
151
152
if (document.getElementById('history')?.checked) {
152
153
if (filter_last_message) {
153
154
messages = [messages.pop()];
@@ -160,7 +161,7 @@ const filter_messages = (messages, filter_last_message = true) => {
160
161
for (i in messages) {
161
162
new_message = messages[i];
162
163
// Remove generated images from history
163
new_message["content"] = new_message["content"].replaceAll(
164
new_message.content = new_message.content.replaceAll(
164
165
/<!-- generated images start -->[\s\S]+<!-- generated images end -->/gm,
165
166
""
166
167
)
@@ -171,6 +172,15 @@ const filter_messages = (messages, filter_last_message = true) => {
171
172
}
172
173
}
173
174
175
// Add system message
176
system_content = systemPrompt?.value;
177
if (system_content) {
178
new_messages.unshift({
179
"role": "system",
180
"content": system_content
181
});
182
}
183
174
184
return new_messages;
175
185
}
176
186
@@ -179,7 +189,7 @@ const ask_gpt = async () => {
179
189
messages = await get_messages(window.conversation_id);
180
190
total_messages = messages.length;
181
191
182
messages = filter_messages(messages);
192
messages = prepare_messages(messages);
183
193
184
194
window.scrollTo(0, 0);
185
195
window.controller = new AbortController();
@@ -192,8 +202,6 @@ const ask_gpt = async () => {
192
202
193
203
message_box.scrollTop = message_box.scrollHeight;
194
204
window.scrollTo(0, 0);
195
await new Promise((r) => setTimeout(r, 500));
196
window.scrollTo(0, 0);
197
205
198
206
el = message_box.querySelector('.count_total');
199
207
el ? el.parentElement.removeChild(el) : null;
@@ -218,6 +226,8 @@ const ask_gpt = async () => {
218
226
219
227
message_box.scrollTop = message_box.scrollHeight;
220
228
window.scrollTo(0, 0);
229
230
error = provider_result = null;
221
231
try {
222
232
let body = JSON.stringify({
223
233
id: window.token,
@@ -241,18 +251,14 @@ const ask_gpt = async () => {
241
251
} else {
242
252
headers['content-type'] = 'application/json';
243
253
}
254
244
255
const response = await fetch(`/backend-api/v2/conversation`, {
245
256
method: 'POST',
246
257
signal: window.controller.signal,
247
258
headers: headers,
248
259
body: body
249
260
});
250
251
await new Promise((r) => setTimeout(r, 1000));
252
window.scrollTo(0, 0);
253
254
261
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
255
error = provider = null;
256
262
while (true) {
257
263
const { value, done } = await reader.read();
258
264
if (done) break;
@@ -262,12 +268,12 @@ const ask_gpt = async () => {
262
268
if (message.type == "content") {
263
269
text += message.content;
264
270
} else if (message["type"] == "provider") {
265
provider = message.provider
271
provider_result = message.provider
266
272
content.querySelector('.provider').innerHTML = `
267
<a href="${provider.url}" target="_blank">
268
${provider.name}
273
<a href="${provider_result.url}" target="_blank">
274
${provider_result.name}
269
275
</a>
270
${provider.model ? ' with ' + provider.model : ''}
276
${provider_result.model ? ' with ' + provider_result.model : ''}
271
277
`
272
278
} else if (message["type"] == "error") {
273
279
error = message["error"];
@@ -292,7 +298,7 @@ const ask_gpt = async () => {
292
298
html = html.substring(0, lastIndex) + '<span id="cursor"></span>' + lastElement;
293
299
}
294
300
content_inner.innerHTML = html;
295
content_count.innerText = count_words_and_tokens(text, provider?.model);
301
content_count.innerText = count_words_and_tokens(text, provider_result?.model);
296
302
highlight(content_inner);
297
303
}
298
304
@@ -324,19 +330,19 @@ const ask_gpt = async () => {
324
330
}
325
331
}
326
332
if (!error) {
327
await add_message(window.conversation_id, "assistant", text, provider);
333
await add_message(window.conversation_id, "assistant", text, provider_result);
328
334
await load_conversation(window.conversation_id);
329
335
} else {
330
336
let cursorDiv = document.getElementById(`cursor`);
331
337
if (cursorDiv) cursorDiv.parentNode.removeChild(cursorDiv);
332
338
}
339
window.scrollTo(0, 0);
333
340
message_box.scrollTop = message_box.scrollHeight;
334
341
await remove_cancel_button();
335
342
await register_remove_message();
336
343
prompt_lock = false;
337
window.scrollTo(0, 0);
338
344
await load_conversations();
339
regenerate.classList.remove(`regenerate-hidden`);
345
regenerate.classList.remove("regenerate-hidden");
340
346
};
341
347
342
348
const clear_conversations = async () => {
@@ -362,6 +368,10 @@ const clear_conversation = async () => {
362
368
while (messages.length > 0) {
363
369
message_box.removeChild(messages[0]);
364
370
}
371
372
if (systemPrompt) {
373
systemPrompt.value = "";
374
}
365
375
};
366
376
367
377
const show_option = async (conversation_id) => {
@@ -418,17 +428,22 @@ const new_conversation = async () => {
418
428
};
419
429
420
430
const load_conversation = async (conversation_id) => {
421
let messages = await get_messages(conversation_id);
431
let conversation = await get_conversation(conversation_id);
432
let messages = conversation?.items || [];
433
434
if (systemPrompt) {
435
systemPrompt.value = conversation.system || "";
436
}
422
437
423
438
let elements = "";
424
439
let last_model = null;
425
440
for (i in messages) {
426
441
let item = messages[i];
427
last_model = item?.provider?.model;
442
last_model = item.provider?.model;
428
443
let next_i = parseInt(i) + 1;
429
444
let next_provider = item.provider ? item.provider : (messages.length > next_i ? messages[next_i].provider : null);
430
445
431
let provider_link = item.provider?.name ? `<a href="${item.provider?.url}" target="_blank">${item.provider.name}</a>` : "";
446
let provider_link = item.provider?.name ? `<a href="${item.provider.url}" target="_blank">${item.provider.name}</a>` : "";
432
447
let provider = provider_link ? `
433
448
<div class="provider">
434
449
${provider_link}
@@ -454,7 +469,7 @@ const load_conversation = async (conversation_id) => {
454
469
`;
455
470
}
456
471
457
const filtered = filter_messages(messages, false);
472
const filtered = prepare_messages(messages, false);
458
473
if (filtered.length > 0) {
459
474
last_model = last_model?.startsWith("gpt-4") ? "gpt-4" : "gpt-3.5-turbo"
460
475
let count_total = GPTTokenizer_cl100k_base?.encodeChat(filtered, last_model).length
@@ -493,19 +508,26 @@ function count_words_and_tokens(text, model) {
493
508
return countWords ? `(${countWords(text)} words${tokens_append})` : "";
494
509
}
495
510
496
const get_conversation = async (conversation_id) => {
511
async function get_conversation(conversation_id) {
497
512
let conversation = await JSON.parse(
498
513
localStorage.getItem(`conversation:${conversation_id}`)
499
514
);
500
515
return conversation;
501
};
516
}
517
518
async function save_conversation(conversation_id, conversation) {
519
localStorage.setItem(
520
`conversation:${conversation_id}`,
521
JSON.stringify(conversation)
522
);
523
}
502
524
503
const get_messages = async (conversation_id) => {
525
async function get_messages(conversation_id) {
504
526
let conversation = await get_conversation(conversation_id);
505
527
return conversation?.items || [];
506
};
528
}
507
529
508
const add_conversation = async (conversation_id, content) => {
530
async function add_conversation(conversation_id, content) {
509
531
if (content.length > 17) {
510
532
title = content.substring(0, 17) + '...'
511
533
} else {
@@ -513,18 +535,23 @@ const add_conversation = async (conversation_id, content) => {
513
535
}
514
536
515
537
if (localStorage.getItem(`conversation:${conversation_id}`) == null) {
516
localStorage.setItem(
517
`conversation:${conversation_id}`,
518
JSON.stringify({
519
id: conversation_id,
520
title: title,
521
items: [],
522
})
523
);
538
await save_conversation(conversation_id, {
539
id: conversation_id,
540
title: title,
541
system: systemPrompt?.value,
542
items: [],
543
});
524
544
}
525
545
526
546
history.pushState({}, null, `/chat/${conversation_id}`);
527
};
547
}
548
549
async function save_system_message() {
550
if (!window.conversation_id) return;
551
const conversation = await get_conversation(window.conversation_id);
552
conversation.system = systemPrompt?.value;
553
await save_conversation(window.conversation_id, conversation);
554
}
528
555
529
556
const hide_last_message = async (conversation_id) => {
530
557
const conversation = await get_conversation(conversation_id)
@@ -533,11 +560,7 @@ const hide_last_message = async (conversation_id) => {
533
560
last_message["regenerate"] = true;
534
561
}
535
562
conversation.items.push(last_message);
536
537
localStorage.setItem(
538
`conversation:${conversation_id}`,
539
JSON.stringify(conversation)
540
);
563
await save_conversation(conversation_id, conversation);
541
564
};
542
565
543
566
const remove_message = async (conversation_id, index) => {
@@ -552,10 +575,7 @@ const remove_message = async (conversation_id, index) => {
552
575
}
553
576
}
554
577
conversation.items = new_items;
555
localStorage.setItem(
556
`conversation:${conversation_id}`,
557
JSON.stringify(conversation)
558
);
578
await save_conversation(conversation_id, conversation);
559
579
};
560
580
561
581
const add_message = async (conversation_id, role, content, provider) => {
@@ -566,12 +586,7 @@ const add_message = async (conversation_id, role, content, provider) => {
566
586
content: content,
567
587
provider: provider
568
588
});
569
570
localStorage.setItem(
571
`conversation:${conversation_id}`,
572
JSON.stringify(conversation)
573
);
574
589
await save_conversation(conversation_id, conversation);
575
590
return conversation.items.length - 1;
576
591
};
577
592
@@ -754,9 +769,7 @@ window.onload = async () => {
754
769
say_hello()
755
770
}
756
771
757
setTimeout(() => {
758
load_conversations();
759
}, 1);
772
load_conversations();
760
773
761
774
message_input.addEventListener("keydown", async (evt) => {
762
775
if (prompt_lock) return;
@@ -875,4 +888,8 @@ fileInput.addEventListener('change', async (event) => {
875
888
} else {
876
889
delete fileInput.dataset.text;
877
890
}
891
});
892
893
systemPrompt?.addEventListener("blur", async () => {
894
await save_system_message();
878
895
});