返回提交历史
Modified
g4f/Provider/Copilot.py
+15
-2
Modified
g4f/Provider/needs_auth/CopilotAccount.py
+2
-1
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+1
-1
Modified
g4f/gui/client/index.html
+4
-5
Modified
g4f/gui/client/static/css/style.css
+17
-15
Modified
g4f/gui/client/static/js/chat.v1.js
+121
-111
XFEstudio/gpt4free
Add image upload to Copilot provider Remove prompt lock from gui Fix discord link in gui
7c10a036
代码差异
6 个文件
+160
-135
@@ -23,9 +23,10 @@ except ImportError:
23
23
24
24
from .base_provider import AbstractProvider, BaseConversation
25
25
from .helper import format_prompt
26
from ..typing import CreateResult, Messages
26
from ..typing import CreateResult, Messages, ImageType
27
27
from ..errors import MissingRequirementsError
28
28
from ..requests.raise_for_status import raise_for_status
29
from ..image import to_bytes, is_accepted_format
29
30
from .. import debug
30
31
31
32
class Conversation(BaseConversation):
@@ -43,6 +44,7 @@ class Copilot(AbstractProvider):
43
44
url = "https://copilot.microsoft.com"
44
45
working = True
45
46
supports_stream = True
47
default_model = "Copilot"
46
48
47
49
websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2"
48
50
conversation_url = f"{url}/c/api/conversations"
@@ -55,6 +57,7 @@ class Copilot(AbstractProvider):
55
57
stream: bool = False,
56
58
proxy: str = None,
57
59
timeout: int = 900,
60
image: ImageType = None,
58
61
conversation: Conversation = None,
59
62
return_conversation: bool = False,
60
63
**kwargs
@@ -98,11 +101,21 @@ class Copilot(AbstractProvider):
98
101
if debug.logging:
99
102
print(f"Copilot: Use conversation: {conversation_id}")
100
103
104
images = []
105
if image is not None:
106
data = to_bytes(image)
107
response = session.post(
108
"https://copilot.microsoft.com/c/api/attachments",
109
headers={"content-type": is_accepted_format(data)},
110
data=data
111
)
112
images.append({"type":"image", "url": response.json().get("url")})
113
101
114
wss = session.ws_connect(cls.websocket_url)
102
115
wss.send(json.dumps({
103
116
"event": "send",
104
117
"conversationId": conversation_id,
105
"content": [{
118
"content": [*images, {
106
119
"type": "text",
107
120
"text": prompt,
108
121
}],
@@ -5,4 +5,5 @@ from ..Copilot import Copilot
5
5
class CopilotAccount(Copilot):
6
6
needs_auth = True
7
7
parent = "Copilot"
8
default_model = ""
8
default_model = "Copilot"
9
default_vision_model = default_model
@@ -396,7 +396,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
396
396
f"{cls.url}/backend-anon/sentinel/chat-requirements"
397
397
if cls._api_key is None else
398
398
f"{cls.url}/backend-api/sentinel/chat-requirements",
399
json={"p": get_requirements_token(RequestConfig.proof_token)},
399
json={"p": get_requirements_token(RequestConfig.proof_token) if RequestConfig.proof_token else None},
400
400
headers=cls._headers
401
401
) as response:
402
402
cls._update_request_args(session)
@@ -72,12 +72,12 @@
72
72
</button>
73
73
<div class="info">
74
74
<i class="fa-brands fa-discord"></i>
75
<span class="convo-title">discord ~ <a href="https://discord.gghttps://discord.gg/6yrm7H4B">discord.gg/6yrm7H4B</a>
75
<span class="convo-title">discord ~ <a href="https://discord.gg/6yrm7H4B" target="_blank">discord.gg/6yrm7H4B</a>
76
76
</span>
77
77
</div>
78
78
<div class="info">
79
79
<i class="fa-brands fa-github"></i>
80
<span class="convo-title">github ~ <a href="https://github.com/xtekky/gpt4free">@xtekky/gpt4free</a>
80
<span class="convo-title">github ~ <a href="https://github.com/xtekky/gpt4free" target="_blank">@xtekky/gpt4free</a>
81
81
</span>
82
82
</div>
83
83
<div class="info">
@@ -87,7 +87,6 @@
87
87
</div>
88
88
</div>
89
89
<div class="images hidden">
90
91
90
</div>
92
91
<div class="settings hidden">
93
92
<div class="paper">
@@ -192,7 +191,7 @@
192
191
<i class="fa-regular fa-stop"></i>
193
192
</button>
194
193
</div>
195
<div class="regenerate regenerate-hidden">
194
<div class="regenerate">
196
195
<button id="regenerateButton">
197
196
<span>Regenerate</span>
198
197
<i class="fa-solid fa-rotate"></i>
@@ -263,4 +262,4 @@
263
262
<i class="fa-solid fa-bars"></i>
264
263
</div>
265
264
</body>
266
</html>
265
</html>
@@ -472,6 +472,11 @@ body {
472
472
padding: 0 4px;
473
473
}
474
474
475
.stop_generating-hidden, .regenerate-hidden {
476
animation: hide_popup 0.4s;
477
display: none;
478
}
479
475
480
.stop_generating, .toolbar .regenerate {
476
481
position: absolute;
477
482
z-index: 1000000;
@@ -479,14 +484,6 @@ body {
479
484
right: 0;
480
485
}
481
486
482
@media only screen and (min-width: 40em) {
483
.stop_generating, .toolbar .regenerate {
484
left: 50%;
485
transform: translateX(-50%);
486
right: auto;
487
}
488
}
489
490
487
.stop_generating button, .toolbar .regenerate button{
491
488
backdrop-filter: blur(20px);
492
489
-webkit-backdrop-filter: blur(20px);
@@ -503,6 +500,17 @@ body {
503
500
animation: show_popup 0.4s;
504
501
}
505
502
503
@media only screen and (min-width: 40em) {
504
.stop_generating {
505
left: 50%;
506
transform: translateX(-50%);
507
right: auto;
508
}
509
.toolbar .regenerate {
510
right: 5px;
511
}
512
}
513
506
514
.toolbar .hide-input {
507
515
background: transparent;
508
516
border: none;
@@ -524,11 +532,6 @@ body {
524
532
}
525
533
}
526
534
527
.stop_generating-hidden #cancelButton, .regenerate-hidden #regenerateButton {
528
animation: hide_popup 0.4s;
529
display: none;
530
}
531
532
535
.typing {
533
536
position: absolute;
534
537
top: -25px;
@@ -779,8 +782,7 @@ select {
779
782
overflow: auto;
780
783
}
781
784
782
783
#cursor {
785
.cursor {
784
786
line-height: 17px;
785
787
margin-left: 3px;
786
788
-webkit-animation: blink 0.8s infinite;
@@ -20,12 +20,13 @@ const settings = document.querySelector(".settings");
20
20
const chat = document.querySelector(".conversation");
21
21
const album = document.querySelector(".images");
22
22
23
let prompt_lock = false;
24
25
let content, content_inner, content_count = null;
26
27
23
const optionElements = document.querySelectorAll(".settings input, .settings textarea, #model, #model2, #provider")
28
24
25
let provider_storage = {};
26
let message_storage = {};
27
let controller_storage = {};
28
let content_storage = {}
29
29
30
messageInput.addEventListener("blur", () => {
30
31
window.scrollTo(0, 0);
31
32
});
@@ -98,13 +99,10 @@ const register_message_buttons = async () => {
98
99
if (!("click" in el.dataset)) {
99
100
el.dataset.click = "true";
100
101
el.addEventListener("click", async () => {
101
if (prompt_lock) {
102
return;
103
}
104
102
const message_el = el.parentElement.parentElement;
105
103
await remove_message(window.conversation_id, message_el.dataset.index);
106
await load_conversation(window.conversation_id, false);
107
})
104
await safe_load_conversation(window.conversation_id, false);
105
});
108
106
}
109
107
});
110
108
@@ -114,18 +112,16 @@ const register_message_buttons = async () => {
114
112
el.addEventListener("click", async () => {
115
113
const message_el = el.parentElement.parentElement.parentElement;
116
114
const copyText = await get_message(window.conversation_id, message_el.dataset.index);
117
118
try {
119
if (!navigator.clipboard) {
120
throw new Error("navigator.clipboard: Clipboard API unavailable.");
115
try {
116
if (!navigator.clipboard) {
117
throw new Error("navigator.clipboard: Clipboard API unavailable.");
118
}
119
await navigator.clipboard.writeText(copyText);
120
} catch (e) {
121
console.error(e);
122
console.error("Clipboard API writeText() failed! Fallback to document.exec(\"copy\")...");
123
fallback_clipboard(copyText);
121
124
}
122
await navigator.clipboard.writeText(copyText);
123
} catch (e) {
124
console.error(e);
125
console.error("Clipboard API writeText() failed! Fallback to document.exec(\"copy\")...");
126
fallback_clipboard(copyText);
127
}
128
129
125
el.classList.add("clicked");
130
126
setTimeout(() => el.classList.remove("clicked"), 1000);
131
127
})
@@ -217,10 +213,8 @@ const register_message_buttons = async () => {
217
213
const message_el = el.parentElement.parentElement.parentElement;
218
214
el.classList.add("clicked");
219
215
setTimeout(() => el.classList.remove("clicked"), 1000);
220
prompt_lock = true;
221
216
await hide_message(window.conversation_id, message_el.dataset.index);
222
window.token = message_id();
223
await ask_gpt(message_el.dataset.index);
217
await ask_gpt(message_el.dataset.index, get_message_id());
224
218
})
225
219
}
226
220
});
@@ -264,12 +258,11 @@ const handle_ask = async () => {
264
258
messageInput.focus();
265
259
window.scrollTo(0, 0);
266
260
267
message = messageInput.value
261
let message = messageInput.value;
268
262
if (message.length <= 0) {
269
263
return;
270
264
}
271
265
messageInput.value = "";
272
prompt_lock = true;
273
266
count_input()
274
267
await add_conversation(window.conversation_id, message);
275
268
@@ -279,7 +272,7 @@ const handle_ask = async () => {
279
272
message += '\n```'
280
273
}
281
274
let message_index = await add_message(window.conversation_id, "user", message);
282
window.token = message_id();
275
let message_id = get_message_id();
283
276
284
277
if (imageInput.dataset.src) URL.revokeObjectURL(imageInput.dataset.src);
285
278
const input = imageInput && imageInput.files.length > 0 ? imageInput : cameraInput
@@ -293,7 +286,7 @@ const handle_ask = async () => {
293
286
<i class="fa-solid fa-xmark"></i>
294
287
<i class="fa-regular fa-phone-arrow-up-right"></i>
295
288
</div>
296
<div class="content" id="user_${token}">
289
<div class="content" id="user_${message_id}">
297
290
<div class="content_inner">
298
291
${markdown_render(message)}
299
292
${imageInput.dataset.src
@@ -312,17 +305,38 @@ const handle_ask = async () => {
312
305
</div>
313
306
`;
314
307
highlight(message_box);
315
await ask_gpt();
308
stop_generating.classList.remove("stop_generating-hidden");
309
await ask_gpt(-1, message_id);
316
310
};
317
311
318
const remove_cancel_button = async () => {
319
stop_generating.classList.add(`stop_generating-hiding`);
312
async function remove_cancel_button() {
313
stop_generating.classList.add("stop_generating-hidden");
314
}
315
316
regenerate.addEventListener("click", async () => {
317
regenerate.classList.add("regenerate-hidden");
318
stop_generating.classList.remove("stop_generating-hidden");
319
await hide_message(window.conversation_id);
320
await ask_gpt(-1, get_message_id());
321
});
320
322
321
setTimeout(() => {
322
stop_generating.classList.remove(`stop_generating-hiding`);
323
stop_generating.classList.add(`stop_generating-hidden`);
324
}, 300);
325
};
323
stop_generating.addEventListener("click", async () => {
324
stop_generating.classList.add("stop_generating-hidden");
325
regenerate.classList.remove("regenerate-hidden");
326
let key;
327
for (key in controller_storage) {
328
if (!controller_storage[key].signal.aborted) {
329
controller_storage[key].abort();
330
let message = message_storage[key];
331
if (message) {
332
content_storage[key].inner.innerHTML += " [aborted]";
333
message_storage[key] += " [aborted]";
334
console.log(`aborted ${window.conversation_id} #${key}`);
335
}
336
}
337
}
338
await load_conversation(window.conversation_id);
339
});
326
340
327
341
const prepare_messages = (messages, message_index = -1) => {
328
342
// Removes none user messages at end
@@ -367,13 +381,13 @@ const prepare_messages = (messages, message_index = -1) => {
367
381
return new_messages;
368
382
}
369
383
370
371
async function add_message_chunk(message) {
384
async function add_message_chunk(message, message_index) {
385
content_map = content_storage[message_index];
372
386
if (message.type == "conversation") {
373
387
console.info("Conversation used:", message.conversation)
374
388
} else if (message.type == "provider") {
375
window.provider_result = message.provider;
376
content.querySelector('.provider').innerHTML = `
389
provider_storage[message_index] = message.provider;
390
content_map.content.querySelector('.provider').innerHTML = `
377
391
<a href="${message.provider.url}" target="_blank">
378
392
${message.provider.label ? message.provider.label : message.provider.name}
379
393
</a>
@@ -384,12 +398,12 @@ async function add_message_chunk(message) {
384
398
} else if (message.type == "error") {
385
399
window.error = message.error
386
400
console.error(message.error);
387
content_inner.innerHTML += `<p><strong>An error occured:</strong> ${message.error}</p>`;
401
content_map.inner.innerHTML += `<p><strong>An error occured:</strong> ${message.error}</p>`;
388
402
} else if (message.type == "preview") {
389
content_inner.innerHTML = markdown_render(message.preview);
403
content_map.inner.innerHTML = markdown_render(message.preview);
390
404
} else if (message.type == "content") {
391
window.text += message.content;
392
html = markdown_render(window.text);
405
message_storage[message_index] += message.content;
406
html = markdown_render(message_storage[message_index]);
393
407
let lastElement, lastIndex = null;
394
408
for (element of ['</p>', '</code></pre>', '</p>\n</li>\n</ol>', '</li>\n</ol>', '</li>\n</ul>']) {
395
409
const index = html.lastIndexOf(element)
@@ -399,11 +413,11 @@ async function add_message_chunk(message) {
399
413
}
400
414
}
401
415
if (lastIndex) {
402
html = html.substring(0, lastIndex) + '<span id="cursor"></span>' + lastElement;
416
html = html.substring(0, lastIndex) + '<span class="cursor"></span>' + lastElement;
403
417
}
404
content_inner.innerHTML = html;
405
content_count.innerText = count_words_and_tokens(text, window.provider_result?.model);
406
highlight(content_inner);
418
content_map.inner.innerHTML = html;
419
content_map.count.innerText = count_words_and_tokens(message_storage[message_index], provider_storage[message_index]?.model);
420
highlight(content_map.inner);
407
421
}
408
422
window.scrollTo(0, 0);
409
423
if (message_box.scrollTop >= message_box.scrollHeight - message_box.clientHeight - 100) {
@@ -411,13 +425,6 @@ async function add_message_chunk(message) {
411
425
}
412
426
}
413
427
414
// fileInput?.addEventListener("click", (e) => {
415
// if (window?.pywebview) {
416
// e.preventDefault();
417
// pywebview.api.choose_file();
418
// }
419
// });
420
421
428
cameraInput?.addEventListener("click", (e) => {
422
429
if (window?.pywebview) {
423
430
e.preventDefault();
@@ -432,44 +439,44 @@ imageInput?.addEventListener("click", (e) => {
432
439
}
433
440
});
434
441
435
const ask_gpt = async (message_index = -1) => {
436
regenerate.classList.add(`regenerate-hidden`);
437
messages = await get_messages(window.conversation_id);
438
total_messages = messages.length;
442
const ask_gpt = async (message_index = -1, message_id) => {
443
let messages = await get_messages(window.conversation_id);
444
let total_messages = messages.length;
439
445
messages = prepare_messages(messages, message_index);
440
441
stop_generating.classList.remove(`stop_generating-hidden`);
446
message_index = total_messages
447
message_storage[message_index] = "";
448
stop_generating.classList.remove(".stop_generating-hidden");
442
449
443
450
message_box.scrollTop = message_box.scrollHeight;
444
451
window.scrollTo(0, 0);
445
452
446
el = message_box.querySelector('.count_total');
447
el ? el.parentElement.removeChild(el) : null;
453
let count_total = message_box.querySelector('.count_total');
454
count_total ? count_total.parentElement.removeChild(count_total) : null;
448
455
449
456
message_box.innerHTML += `
450
<div class="message" data-index="${total_messages}">
457
<div class="message" data-index="${message_index}">
451
458
<div class="assistant">
452
459
${gpt_image}
453
460
<i class="fa-solid fa-xmark"></i>
454
461
<i class="fa-regular fa-phone-arrow-down-left"></i>
455
462
</div>
456
<div class="content" id="gpt_${window.token}">
463
<div class="content" id="gpt_${message_id}">
457
464
<div class="provider"></div>
458
<div class="content_inner"><span id="cursor"></span></div>
465
<div class="content_inner"><span class="cursor"></span></div>
459
466
<div class="count"></div>
460
467
</div>
461
468
</div>
462
469
`;
463
470
464
window.controller = new AbortController();
465
window.text = "";
466
window.error = null;
467
window.abort = false;
468
window.provider_result = null;
471
controller_storage[message_index] = new AbortController();
472
let error = false;
469
473
470
content = document.getElementById(`gpt_${window.token}`);
471
content_inner = content.querySelector('.content_inner');
472
content_count = content.querySelector('.count');
474
let content_el = document.getElementById(`gpt_${message_id}`)
475
let content_map = content_storage[message_index] = {
476
content: content_el,
477
inner: content_el.querySelector('.content_inner'),
478
count: content_el.querySelector('.count'),
479
}
473
480
474
481
message_box.scrollTop = message_box.scrollHeight;
475
482
window.scrollTo(0, 0);
@@ -478,8 +485,6 @@ const ask_gpt = async (message_index = -1) => {
478
485
const file = input && input.files.length > 0 ? input.files[0] : null;
479
486
const provider = providerSelect.options[providerSelect.selectedIndex].value;
480
487
const auto_continue = document.getElementById("auto_continue")?.checked;
481
if (file && !provider)
482
provider = "Bing";
483
488
let api_key = null;
484
489
if (provider) {
485
490
api_key = document.getElementById(`${provider}-api_key`)?.value || null;
@@ -487,7 +492,7 @@ const ask_gpt = async (message_index = -1) => {
487
492
api_key = document.querySelector(`.${provider}-api_key`)?.value || null;
488
493
}
489
494
await api("conversation", {
490
id: window.token,
495
id: message_id,
491
496
conversation_id: window.conversation_id,
492
497
model: get_selected_model(),
493
498
web_search: document.getElementById("switch").checked,
@@ -495,11 +500,11 @@ const ask_gpt = async (message_index = -1) => {
495
500
messages: messages,
496
501
auto_continue: auto_continue,
497
502
api_key: api_key
498
}, file);
503
}, file, message_index);
499
504
if (!error) {
500
html = markdown_render(text);
501
content_inner.innerHTML = html;
502
highlight(content_inner);
505
html = markdown_render(message_storage[message_index]);
506
content_map.inner.innerHTML = html;
507
highlight(content_map.inner);
503
508
504
509
if (imageInput) imageInput.value = "";
505
510
if (cameraInput) cameraInput.value = "";
@@ -509,23 +514,23 @@ const ask_gpt = async (message_index = -1) => {
509
514
console.error(e);
510
515
if (e.name != "AbortError") {
511
516
error = true;
512
content_inner.innerHTML += `<p><strong>An error occured:</strong> ${e}</p>`;
517
content_map.inner.innerHTML += `<p><strong>An error occured:</strong> ${e}</p>`;
513
518
}
514
519
}
515
if (!error && text) {
516
await add_message(window.conversation_id, "assistant", text, provider_result);
517
await load_conversation(window.conversation_id);
520
delete controller_storage[message_index];
521
if (!error && message_storage[message_index]) {
522
const message_provider = message_index in provider_storage ? provider_storage[message_index] : null;
523
await add_message(window.conversation_id, "assistant", message_storage[message_index], message_provider);
524
await safe_load_conversation(window.conversation_id);
518
525
} else {
519
let cursorDiv = document.getElementById("cursor");
526
let cursorDiv = message_box.querySelector(".cursor");
520
527
if (cursorDiv) cursorDiv.parentNode.removeChild(cursorDiv);
521
528
}
522
529
window.scrollTo(0, 0);
523
530
message_box.scrollTop = message_box.scrollHeight;
524
531
await remove_cancel_button();
525
532
await register_message_buttons();
526
prompt_lock = false;
527
533
await load_conversations();
528
regenerate.classList.remove("regenerate-hidden");
529
534
};
530
535
531
536
const clear_conversations = async () => {
@@ -645,6 +650,10 @@ const load_conversation = async (conversation_id, scroll=true) => {
645
650
let conversation = await get_conversation(conversation_id);
646
651
let messages = conversation?.items || [];
647
652
653
if (!conversation) {
654
return;
655
}
656
648
657
if (systemPrompt) {
649
658
systemPrompt.value = conversation.system || "";
650
659
}
@@ -714,6 +723,19 @@ const load_conversation = async (conversation_id, scroll=true) => {
714
723
}
715
724
};
716
725
726
async function safe_load_conversation(conversation_id, scroll=true) {
727
let is_running = false
728
for (const key in controller_storage) {
729
if (!controller_storage[key].signal.aborted) {
730
is_running = true;
731
break
732
}
733
}
734
if (!is_running) {
735
load_conversation(conversation_id, scroll);
736
}
737
}
738
717
739
async function get_conversation(conversation_id) {
718
740
let conversation = await JSON.parse(
719
741
appStorage.getItem(`conversation:${conversation_id}`)
@@ -852,23 +874,6 @@ const load_conversations = async () => {
852
874
box_conversations.innerHTML += html;
853
875
};
854
876
855
document.getElementById("cancelButton").addEventListener("click", async () => {
856
window.controller.abort();
857
if (!window.abort) {
858
window.abort = true;
859
content_inner.innerHTML += " [aborted]";
860
if (window.text) window.text += " [aborted]";
861
}
862
console.log(`aborted ${window.conversation_id}`);
863
});
864
865
document.getElementById("regenerateButton").addEventListener("click", async () => {
866
prompt_lock = true;
867
await hide_message(window.conversation_id);
868
window.token = message_id();
869
await ask_gpt();
870
});
871
872
877
const hide_input = document.querySelector(".toolbar .hide-input");
873
878
hide_input.addEventListener("click", async (e) => {
874
879
const icon = hide_input.querySelector("i");
@@ -891,7 +896,7 @@ const uuid = () => {
891
896
);
892
897
};
893
898
894
const message_id = () => {
899
function get_message_id() {
895
900
random_bytes = (Math.floor(Math.random() * 1338377565) + 2956589730).toString(
896
901
2
897
902
);
@@ -1124,6 +1129,7 @@ async function on_load() {
1124
1129
}
1125
1130
1126
1131
async function on_api() {
1132
let prompt_lock = false;
1127
1133
messageInput.addEventListener("keydown", async (evt) => {
1128
1134
if (prompt_lock) return;
1129
1135
@@ -1132,6 +1138,8 @@ async function on_api() {
1132
1138
if (evt.keyCode === 13 && !evt.shiftKey) {
1133
1139
evt.preventDefault();
1134
1140
console.log("pressed enter");
1141
prompt_lock = true;
1142
setTimeout(()=>prompt_lock=false, 3);
1135
1143
await handle_ask();
1136
1144
} else {
1137
1145
messageInput.style.removeProperty("height");
@@ -1141,6 +1149,8 @@ async function on_api() {
1141
1149
sendButton.addEventListener(`click`, async () => {
1142
1150
console.log("clicked send");
1143
1151
if (prompt_lock) return;
1152
prompt_lock = true;
1153
setTimeout(()=>prompt_lock=false, 3);
1144
1154
await handle_ask();
1145
1155
});
1146
1156
messageInput.focus();
@@ -1289,7 +1299,7 @@ function get_selected_model() {
1289
1299
}
1290
1300
}
1291
1301
1292
async function api(ressource, args=null, file=null) {
1302
async function api(ressource, args=null, file=null, message_index=null) {
1293
1303
if (window?.pywebview) {
1294
1304
if (args !== null) {
1295
1305
if (ressource == "models") {
@@ -1318,17 +1328,17 @@ async function api(ressource, args=null, file=null) {
1318
1328
}
1319
1329
response = await fetch(url, {
1320
1330
method: 'POST',
1321
signal: window.controller.signal,
1331
signal: controller_storage[message_index].signal,
1322
1332
headers: headers,
1323
1333
body: body
1324
1334
});
1325
return read_response(response);
1335
return read_response(response, message_index);
1326
1336
}
1327
1337
response = await fetch(url);
1328
1338
return await response.json();
1329
1339
}
1330
1340
1331
async function read_response(response) {
1341
async function read_response(response, message_index) {
1332
1342
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
1333
1343
let buffer = ""
1334
1344
while (true) {
@@ -1341,7 +1351,7 @@ async function read_response(response) {
1341
1351
continue;
1342
1352
}
1343
1353
try {
1344
add_message_chunk(JSON.parse(buffer + line))
1354
add_message_chunk(JSON.parse(buffer + line), message_index);
1345
1355
buffer = "";
1346
1356
} catch {
1347
1357
buffer += line
@@ -1382,7 +1392,7 @@ providerSelect.addEventListener("change", () => load_provider_models());
1382
1392
function save_storage() {
1383
1393
let filename = `chat ${new Date().toLocaleString()}.json`.replaceAll(":", "-");
1384
1394
let data = {"options": {"g4f": ""}};
1385
for (let i = 0; i < appStorage.length; i++){label
1395
for (let i = 0; i < appStorage.length; i++) {
1386
1396
let key = appStorage.key(i);
1387
1397
let item = appStorage.getItem(key);
1388
1398
if (key.startsWith("conversation:")) {