XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Add image upload to Copilot provider Remove prompt lock from gui Fix discord link in gui

7c10a036
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

6 个文件 +160 -135
Modified g4f/Provider/Copilot.py +15 -2
@@ -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 }],
Modified g4f/Provider/needs_auth/CopilotAccount.py +2 -1
@@ -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
Modified g4f/Provider/needs_auth/OpenaiChat.py +1 -1
@@ -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)
Modified g4f/gui/client/index.html +4 -5
@@ -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>
Modified g4f/gui/client/static/css/style.css +17 -15
@@ -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;
Modified g4f/gui/client/static/js/chat.v1.js +121 -111
@@ -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:")) {