返回提交历史
Modified
g4f/Provider/Cloudflare.py
+17
-2
Modified
g4f/Provider/DDG.py
+37
-11
Modified
g4f/Provider/needs_auth/HuggingFace.py
+1
-1
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+8
-7
Modified
g4f/api/__init__.py
+3
-2
Modified
g4f/gui/client/home.html
+81
-14
Modified
g4f/gui/client/index.html
+2
-1
Modified
g4f/gui/client/static/css/style.css
+17
-4
Modified
g4f/gui/client/static/js/chat.v1.js
+33
-27
Modified
g4f/gui/server/backend_api.py
+26
-9
Modified
g4f/providers/base_provider.py
+20
-4
Modified
g4f/providers/response.py
+3
-1
Modified
g4f/providers/retry_provider.py
+5
-3
Modified
g4f/requests/__init__.py
+13
-1
Modified
g4f/tools/files.py
+77
-46
Modified
g4f/tools/web_search.py
+21
-6
XFEstudio/gpt4free
Add Edge as Browser for nodriver Fix for RetryProviders doesn't retry Add retry and continue for DuckDuckGo provider Add cache for Cloudflare provider Add cache for prompts on gui home Add scroll to bottom checkbox in gui Improve prompts on home gui Fix response content type in api for files
12c413fd
代码差异
16 个文件
+364
-139
@@ -2,12 +2,14 @@ from __future__ import annotations
2
2
3
3
import asyncio
4
4
import json
5
from pathlib import Path
5
6
6
7
from ..typing import AsyncResult, Messages, Cookies
7
8
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin, get_running_loop
8
9
from ..requests import Session, StreamSession, get_args_from_nodriver, raise_for_status, merge_cookies
9
10
from ..requests import DEFAULT_HEADERS, has_nodriver, has_curl_cffi
10
11
from ..providers.response import FinishReason
12
from ..cookies import get_cookies_dir
11
13
from ..errors import ResponseStatusError, ModelNotFoundError
12
14
13
15
class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
@@ -19,7 +21,7 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
19
21
supports_stream = True
20
22
supports_system_message = True
21
23
supports_message_history = True
22
default_model = "@cf/meta/llama-3.1-8b-instruct"
24
default_model = "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
23
25
model_aliases = {
24
26
"llama-2-7b": "@cf/meta/llama-2-7b-chat-fp16",
25
27
"llama-2-7b": "@cf/meta/llama-2-7b-chat-int8",
@@ -33,6 +35,10 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
33
35
}
34
36
_args: dict = None
35
37
38
@classmethod
39
def get_cache_file(cls) -> Path:
40
return Path(get_cookies_dir()) / f"auth_{cls.parent if hasattr(cls, 'parent') else cls.__name__}.json"
41
36
42
@classmethod
37
43
def get_models(cls) -> str:
38
44
if not cls.models:
@@ -67,7 +73,11 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
67
73
timeout: int = 300,
68
74
**kwargs
69
75
) -> AsyncResult:
76
cache_file = cls.get_cache_file()
70
77
if cls._args is None:
78
if cache_file.exists():
79
with cache_file.open("r") as f:
80
cls._args = json.load(f)
71
81
if has_nodriver:
72
82
cls._args = await get_args_from_nodriver(cls.url, proxy, timeout, cookies)
73
83
else:
@@ -93,6 +103,8 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
93
103
await raise_for_status(response)
94
104
except ResponseStatusError:
95
105
cls._args = None
106
if cache_file.exists():
107
cache_file.unlink()
96
108
raise
97
109
reason = None
98
110
async for line in response.iter_lines():
@@ -109,4 +121,7 @@ class Cloudflare(AsyncGeneratorProvider, ProviderModelMixin):
109
121
except Exception:
110
122
continue
111
123
if reason is not None:
112
yield FinishReason(reason)
124
yield FinishReason(reason)
125
126
with cache_file.open("w") as f:
127
json.dump(cls._args, f)
@@ -1,14 +1,18 @@
1
1
from __future__ import annotations
2
2
3
from aiohttp import ClientSession, ClientTimeout, ClientError
3
import asyncio
4
from aiohttp import ClientSession, ClientTimeout, ClientError, ClientResponseError
4
5
import json
6
5
7
from ..typing import AsyncResult, Messages
6
8
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin, BaseConversation
7
from .helper import format_prompt
9
from ..providers.response import FinishReason
10
from .. import debug
8
11
9
12
class Conversation(BaseConversation):
10
13
vqd: str = None
11
14
message_history: Messages = []
15
cookies: dict = {}
12
16
13
17
def __init__(self, model: str):
14
18
self.model = model
@@ -65,20 +69,24 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin):
65
69
conversation: Conversation = None,
66
70
return_conversation: bool = False,
67
71
proxy: str = None,
72
headers: dict = {
73
"Content-Type": "application/json",
74
},
75
cookies: dict = None,
76
max_retries: int = 3,
68
77
**kwargs
69
78
) -> AsyncResult:
70
headers = {
71
"Content-Type": "application/json",
72
}
73
async with ClientSession(headers=headers, timeout=ClientTimeout(total=30)) as session:
79
if cookies is None and conversation is not None:
80
cookies = conversation.cookies
81
async with ClientSession(headers=headers, cookies=cookies, timeout=ClientTimeout(total=30)) as session:
74
82
# Fetch VQD token
75
83
if conversation is None:
76
84
conversation = Conversation(model)
77
78
if conversation.vqd is None:
85
conversation.cookies = session.cookie_jar
79
86
conversation.vqd = await cls.fetch_vqd(session)
80
87
81
headers["x-vqd-4"] = conversation.vqd
88
if conversation.vqd is not None:
89
headers["x-vqd-4"] = conversation.vqd
82
90
83
91
if return_conversation:
84
92
yield conversation
@@ -97,15 +105,33 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin):
97
105
async with session.post(cls.api_endpoint, headers=headers, json=payload, proxy=proxy) as response:
98
106
conversation.vqd = response.headers.get("x-vqd-4")
99
107
response.raise_for_status()
108
reason = None
100
109
async for line in response.content:
101
110
line = line.decode("utf-8").strip()
102
111
if line.startswith("data:"):
103
112
try:
104
113
message = json.loads(line[5:].strip())
105
if "message" in message:
106
yield message["message"]
114
if "message" in message and message["message"]:
115
yield message["message"]
116
reason = "max_tokens"
117
elif message.get("message") == '':
118
reason = "stop"
107
119
except json.JSONDecodeError:
108
120
continue
121
if reason is not None:
122
yield FinishReason(reason)
123
except ClientResponseError as e:
124
if e.code in (400, 429) and max_retries > 0:
125
debug.log(f"Retry: max_retries={max_retries}, wait={512 - max_retries * 48}: {e}")
126
await asyncio.sleep(512 - max_retries * 48)
127
is_started = False
128
async for chunk in cls.create_async_generator(model, messages, conversation, return_conversation, max_retries=max_retries-1, **kwargs):
129
if chunk:
130
yield chunk
131
is_started = True
132
if is_started:
133
return
134
raise e
109
135
except ClientError as e:
110
136
raise Exception(f"HTTP ClientError occurred: {e}")
111
137
except asyncio.TimeoutError:
@@ -137,7 +137,7 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
137
137
else:
138
138
is_special = True
139
139
debug.log(f"Special token: {is_special}")
140
yield FinishReason("stop" if is_special else "max_tokens", actions=["variant"] if is_special else ["continue", "variant"])
140
yield FinishReason("stop" if is_special else "length", actions=["variant"] if is_special else ["continue", "variant"])
141
141
else:
142
142
if response.headers["content-type"].startswith("image/"):
143
143
base64_data = base64.b64encode(b"".join([chunk async for chunk in response.iter_content()]))
@@ -105,11 +105,11 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
105
105
_expires: int = None
106
106
107
107
@classmethod
108
async def on_auth_async(cls, **kwargs) -> AuthResult:
108
async def on_auth_async(cls, **kwargs) -> AsyncIterator:
109
109
if cls.needs_auth:
110
async for _ in cls.login():
111
pass
112
return AuthResult(
110
async for chunk in cls.login():
111
yield chunk
112
yield AuthResult(
113
113
api_key=cls._api_key,
114
114
cookies=cls._cookies or RequestConfig.cookies or {},
115
115
headers=cls._headers or RequestConfig.headers or cls.get_default_headers(),
@@ -174,7 +174,8 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
174
174
"use_case": "multimodal"
175
175
}
176
176
# Post the image data to the service and get the image data
177
async with session.post(f"{cls.url}/backend-api/files", json=data, headers=auth_result.headers) as response:
177
headers = auth_result.headers if hasattr(auth_result, "headers") else None
178
async with session.post(f"{cls.url}/backend-api/files", json=data, headers=headers) as response:
178
179
cls._update_request_args(auth_result, session)
179
180
await raise_for_status(response, "Create file failed")
180
181
image_data = {
@@ -360,7 +361,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
360
361
f"{cls.url}/backend-anon/sentinel/chat-requirements"
361
362
if cls._api_key is None else
362
363
f"{cls.url}/backend-api/sentinel/chat-requirements",
363
json={"p": None if auth_result.proof_token is None else get_requirements_token(auth_result.proof_token)},
364
json={"p": None if not getattr(auth_result, "proof_token") else get_requirements_token(auth_result.proof_token)},
364
365
headers=cls._headers
365
366
) as response:
366
367
if response.status == 401:
@@ -386,7 +387,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
386
387
proofofwork = generate_proof_token(
387
388
**chat_requirements["proofofwork"],
388
389
user_agent=auth_result.headers.get("user-agent"),
389
proof_token=auth_result.proof_token
390
proof_token=getattr(auth_result, "proof_token")
390
391
)
391
392
[debug.log(text) for text in (
392
393
#f"Arkose: {'False' if not need_arkose else auth_result.arkose_token[:12]+'...'}",
@@ -41,7 +41,7 @@ from g4f.errors import ProviderNotFoundError, ModelNotFoundError, MissingAuthErr
41
41
from g4f.cookies import read_cookie_files, get_cookies_dir
42
42
from g4f.Provider import ProviderType, ProviderUtils, __providers__
43
43
from g4f.gui import get_gui_app
44
from g4f.tools.files import supports_filename, get_streaming
44
from g4f.tools.files import supports_filename, get_async_streaming
45
45
from .stubs import (
46
46
ChatCompletionsConfig, ImageGenerationConfig,
47
47
ProviderResponseModel, ModelResponseModel,
@@ -436,7 +436,8 @@ class Api:
436
436
event_stream = "text/event-stream" in request.headers.get("accept", "")
437
437
if not os.path.isdir(bucket_dir):
438
438
return ErrorResponse.from_message("Bucket dir not found", 404)
439
return StreamingResponse(get_streaming(bucket_dir, delete_files, refine_chunks_with_spacy, event_stream), media_type="text/plain")
439
return StreamingResponse(get_async_streaming(bucket_dir, delete_files, refine_chunks_with_spacy, event_stream),
440
media_type="text/event-stream" if event_stream else "text/plain")
440
441
441
442
@self.app.post("/v1/files/{bucket_id}", responses={
442
443
HTTP_200_OK: {"model": UploadResponseModel}
@@ -103,17 +103,29 @@
103
103
z-index: -1;
104
104
}
105
105
106
iframe.stream {
106
.stream-widget {
107
107
max-height: 0;
108
108
transition: max-height 0.15s ease-out;
109
color: var(--colour-5);
110
overflow: scroll;
111
text-align: left;
109
112
}
110
113
111
iframe.stream.show {
114
.stream-widget.show {
112
115
max-height: 1000px;
113
116
height: 1000px;
114
117
transition: max-height 0.25s ease-in;
115
118
background: rgba(255,255,255,0.7);
116
119
border-top: 2px solid rgba(255,255,255,0.5);
120
padding: 20px;
121
}
122
123
.stream-widget img {
124
max-width: 320px;
125
}
126
127
#stream-container {
128
width: 100%;
117
129
}
118
130
119
131
.description {
@@ -207,32 +219,87 @@
207
219
<p>Powered by the G4F framework</p>
208
220
</div>
209
221
210
<iframe id="stream-widget" class="stream" frameborder="0"></iframe>
222
<iframe class="stream-widget" frameborder="0"></iframe>
211
223
</div>
212
224
<script>
213
const iframe = document.getElementById('stream-widget');""
214
let search = (navigator.language == "de" ? "news in deutschland" : navigator.language == "en" ? "world news" : navigator.language);
215
if (Math.floor(Math.random() * 6) % 2 == 0) {
225
const iframe = document.querySelector('.stream-widget');
226
const rand_idx = Math.floor(Math.random() * 9)
227
if (rand_idx < 3) {
216
228
search = "xtekky/gpt4free releases";
229
} else if (rand_idx < 5) {
230
search = "developer news";
231
} else {
232
search = (navigator.language == "de" ? "news in deutsch" : navigator.language == "en" ? "world news" : `news in ${navigator.language}`);
217
233
}
218
const url = "/backend-api/v2/create?prompt=Create of overview of the news in plain text&stream=1&web_search=" + search;
234
const summary_prompt = "Give a summary of the provided text in ```markdown``` format. Add maybe one or more images.";
235
const url = `/backend-api/v2/create?prompt=${summary_prompt}&stream=1&web_search=${search}`;
219
236
iframe.src = url;
220
setTimeout(()=>iframe.classList.add('show'), 3000);
237
const message = "Loading...";
238
setTimeout(()=>{
239
iframe.classList.add('show');
240
const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
241
if (iframeDocument) {
242
const iframeBody = iframeDocument.querySelector("body");
243
if (iframeBody) {
244
iframeBody.innerHTML = message + iframeBody.innerHTML;
245
}
246
} else {
247
iframe.parentElement.removeChild(iframe);
248
}
249
}, 1000);
250
251
function filterMarkdown(text, allowedTypes = null, defaultValue = null) {
252
const match = text.match(/```(.+)\n(?<code>[\s\S]+?)(\n```|$)/);
253
if (match) {
254
const [, type, code] = match;
255
if (!allowedTypes || allowedTypes.includes(type)) {
256
return code;
257
}
258
}
259
return defaultValue;
260
}
261
262
let scroll_to_bottom_callback = () => {
263
const i = document.querySelector(".stream-widget");
264
if (!i.contentWindow || !i.contentDocument) {
265
return;
266
}
267
clientHeight = i.contentDocument.body.scrollHeight;
268
i.contentWindow.scrollTo(0, clientHeight);
269
if (clientHeight - i.contentWindow.scrollY < 2 * clientHeight) {
270
setTimeout(scroll_to_bottom_callback, 1000);
271
}
272
};
273
setTimeout(scroll_to_bottom_callback, 1000);
274
221
275
iframe.onload = () => {
222
276
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
223
const iframeBody = iframeDocument.querySelector("body");
224
277
const iframeContent = iframeDocument.querySelector("pre");
278
let iframeText = iframeContent.innerHTML;
225
279
const markdown = window.markdownit();
226
iframeBody.innerHTML = markdown.render(iframeContent.innerHTML);
280
const iframeContainer = document.querySelector(".container");
281
iframe.remove()
282
if (iframeText.indexOf('"error"') < 0) {
283
iframeContainer.innerHTML += `<div class="stream-widget show">${markdown.render(filterMarkdown(iframeText, "markdown", iframeText))}</div>`;
284
}
285
scroll_to_bottom_callback = () => null;
227
286
}
228
287
229
288
(async () => {
230
const prompt = `
289
const today = new Date().toJSON().slice(0, 10);
290
const max = 100;
291
const cache_id = Math.floor(Math.random() * max);
292
let prompt;
293
if (cache_id % 2 == 0) {
294
prompt = `
231
295
Today is ${new Date().toJSON().slice(0, 10)}.
232
296
Create a single-page HTML screensaver reflecting the current season (based on the date).
233
For example, if it's Spring, it might use floral patterns or pastel colors.
234
Avoid using any text. Consider a subtle animation or transition effect.`;
235
const response = await fetch(`/backend-api/v2/create?prompt=${prompt}&filter_markdown=html`)
297
Avoid using any text.`;
298
} else {
299
prompt = `Create a single-page HTML screensaver. Avoid using any text.`;
300
const response = await fetch(`/backend-api/v2/create?prompt=${prompt}&filter_markdown=html&cache=${cache_id}`);
301
}
302
const response = await fetch(`/backend-api/v2/create?prompt=${prompt}&filter_markdown=html&cache=${cache_id}`);
236
303
const text = await response.text()
237
304
background.src = `data:text/html;charset=utf-8,${encodeURIComponent(text)}`;
238
305
const gradient = document.querySelector('.gradient');
@@ -239,7 +239,8 @@
239
239
<button class="hide-input">
240
240
<i class="fa-solid fa-angles-down"></i>
241
241
</button>
242
<span class="text"></span>
242
<input type="checkbox" id="agree" name="agree" value="yes" checked>
243
<label for="agree" class="text" onclick="this.innerText='';">Scroll to bottom</label>
243
244
</div>
244
245
<div class="stop_generating stop_generating-hidden">
245
246
<button id="cancelButton">
@@ -516,7 +516,11 @@ body:not(.white) a:visited{
516
516
padding: 6px 6px;
517
517
}
518
518
519
#input-count .text {
519
input-count .text {
520
min-width: 12px
521
}
522
523
#input-count .text, #input-count input {
520
524
padding: 0 4px;
521
525
}
522
526
@@ -793,7 +797,7 @@ select {
793
797
appearance: none;
794
798
width: 100%;
795
799
height: 20px;
796
background: var(--accent);
800
background: var(--colour-2);
797
801
outline: none;
798
802
transition: opacity .2s;
799
803
border-radius: 10px;
@@ -859,11 +863,18 @@ select:hover,
859
863
font-size: 15px;
860
864
width: 100%;
861
865
color: var(--colour-3);
862
min-height: 49px;
863
866
height: 59px;
864
867
outline: none;
865
868
padding: var(--inner-gap) var(--section-gap);
866
869
resize: vertical;
870
min-height: 59px;
871
transition: max-height 0.15s ease-out;
872
}
873
874
#systemPrompt:focus {
875
min-height: 200px;
876
max-height: 1000px;
877
transition: max-height 0.25s ease-in;
867
878
}
868
879
869
880
.pswp {
@@ -929,6 +940,9 @@ select:hover,
929
940
body:not(.white) .gradient{
930
941
display: block;
931
942
}
943
.settings .label, form .label, .settings label, form label {
944
min-width: 200px;
945
}
932
946
}
933
947
934
948
.input-box {
@@ -1354,7 +1368,6 @@ form .field.saved .fa-xmark {
1354
1368
.settings .label, form .label, .settings label, form label {
1355
1369
font-size: 15px;
1356
1370
margin-left: var(--inner-gap);
1357
min-width: 200px;
1358
1371
}
1359
1372
1360
1373
.settings .label, form .label {
@@ -511,7 +511,9 @@ const prepare_messages = (messages, message_index = -1, do_continue = false) =>
511
511
// Include only not regenerated messages
512
512
if (new_message && !new_message.regenerate) {
513
513
// Remove generated images from history
514
new_message.content = filter_message(new_message.content);
514
if (new_message.content) {
515
new_message.content = filter_message(new_message.content);
516
}
515
517
// Remove internal fields
516
518
delete new_message.provider;
517
519
delete new_message.synthesize;
@@ -658,7 +660,7 @@ async function load_provider_parameters(provider) {
658
660
}
659
661
}
660
662
661
async function add_message_chunk(message, message_id, provider) {
663
async function add_message_chunk(message, message_id, provider, scroll) {
662
664
content_map = content_storage[message_id];
663
665
if (message.type == "conversation") {
664
666
const conversation = await get_conversation(window.conversation_id);
@@ -698,7 +700,7 @@ async function add_message_chunk(message, message_id, provider) {
698
700
content_map.inner.innerHTML = markdown_render(message.preview);
699
701
} else if (message.type == "content") {
700
702
message_storage[message_id] += message.content;
701
update_message(content_map, message_id);
703
update_message(content_map, message_id, null, scroll);
702
704
content_map.inner.style.height = "";
703
705
} else if (message.type == "log") {
704
706
let p = document.createElement("p");
@@ -709,9 +711,7 @@ async function add_message_chunk(message, message_id, provider) {
709
711
} else if (message.type == "title") {
710
712
title_storage[message_id] = message.title;
711
713
} else if (message.type == "login") {
712
update_message(content_map, message_id, message.login);
713
} else if (message.type == "login") {
714
update_message(content_map, message_id, message.login);
714
update_message(content_map, message_id, message.login, scroll);
715
715
} else if (message.type == "finish") {
716
716
finish_storage[message_id] = message.finish;
717
717
} else if (message.type == "parameters") {
@@ -734,8 +734,11 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
734
734
messages = prepare_messages(conversation.items, message_index, action=="continue");
735
735
message_storage[message_id] = "";
736
736
stop_generating.classList.remove("stop_generating-hidden");
737
738
if (message_index == -1) {
737
const scroll = true;
738
if (message_index > 0 && message_index + 1 < messages.length) {
739
scroll = false;
740
}
741
if (scroll) {
739
742
await lazy_scroll_to_bottom();
740
743
}
741
744
@@ -780,7 +783,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
780
783
update_timeouts: [],
781
784
message_index: message_index,
782
785
}
783
if (message_index == -1) {
786
if (scroll) {
784
787
await lazy_scroll_to_bottom();
785
788
}
786
789
try {
@@ -801,7 +804,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
801
804
download_images: download_images,
802
805
api_key: api_key,
803
806
ignored: ignored,
804
}, files, message_id);
807
}, files, message_id, scroll);
805
808
content_map.update_timeouts.forEach((timeoutId)=>clearTimeout(timeoutId));
806
809
content_map.update_timeouts = [];
807
810
if (!error_storage[message_id]) {
@@ -836,12 +839,12 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
836
839
);
837
840
delete message_storage[message_id];
838
841
if (!error_storage[message_id]) {
839
await safe_load_conversation(window.conversation_id, message_index == -1);
842
await safe_load_conversation(window.conversation_id, scroll);
840
843
}
841
844
}
842
845
let cursorDiv = message_el.querySelector(".cursor");
843
846
if (cursorDiv) cursorDiv.parentNode.removeChild(cursorDiv);
844
if (message_index == -1) {
847
if (scroll) {
845
848
await lazy_scroll_to_bottom();
846
849
}
847
850
await safe_remove_cancel_button();
@@ -856,7 +859,7 @@ async function scroll_to_bottom() {
856
859
}
857
860
858
861
async function lazy_scroll_to_bottom() {
859
if (message_box.scrollHeight - message_box.scrollTop < 2 * message_box.clientHeight) {
862
if (document.querySelector("#input-count input").checked) {
860
863
await scroll_to_bottom();
861
864
}
862
865
}
@@ -1013,6 +1016,8 @@ const load_conversation = async (conversation_id, scroll=true) => {
1013
1016
if (newContent.startsWith("```")) {
1014
1017
const index = str.indexOf("\n");
1015
1018
newContent = newContent.substring(index);
1019
} else if (newContent.startsWith("...")) {
1020
newContent = " " + newContent.substring(3);
1016
1021
}
1017
1022
if (newContent.startsWith(lastLine)) {
1018
1023
newContent = newContent.substring(lastLine.length);
@@ -1054,7 +1059,7 @@ const load_conversation = async (conversation_id, scroll=true) => {
1054
1059
if (item.finish && item.finish.actions) {
1055
1060
actions = item.finish.actions
1056
1061
}
1057
if (!("continue" in actions)) {
1062
if (item.role == "assistant" && !actions.includes("continue")) {
1058
1063
let reason = "stop";
1059
1064
// Read finish reason from conversation
1060
1065
if (item.finish && item.finish.reason) {
@@ -1067,7 +1072,7 @@ const load_conversation = async (conversation_id, scroll=true) => {
1067
1072
reason = "error";
1068
1073
// Has an even number of start or end code tags
1069
1074
} else if (buffer.split("```").length - 1 % 2 === 1) {
1070
reason = "error";
1075
reason = "length";
1071
1076
// Has a end token at the end
1072
1077
} else if (lastLine.endsWith("```") || lastLine.endsWith(".") || lastLine.endsWith("?") || lastLine.endsWith("!")
1073
1078
|| lastLine.endsWith('"') || lastLine.endsWith("'") || lastLine.endsWith(")")
@@ -1152,7 +1157,7 @@ const load_conversation = async (conversation_id, scroll=true) => {
1152
1157
highlight(message_box);
1153
1158
regenerate_button.classList.remove("regenerate-hidden");
1154
1159
1155
if (scroll) {
1160
if (document.querySelector("#input-count input").checked) {
1156
1161
message_box.scrollTo({ top: message_box.scrollHeight, behavior: "smooth" });
1157
1162
1158
1163
setTimeout(() => {
@@ -1517,7 +1522,7 @@ function count_words_and_tokens(text, model) {
1517
1522
return `(${count_words(text)} words, ${count_chars(text)} chars, ${count_tokens(model, text)} tokens)`;
1518
1523
}
1519
1524
1520
function update_message(content_map, message_id, content = null) {
1525
function update_message(content_map, message_id, content = null, scroll = true) {
1521
1526
content_map.update_timeouts.push(setTimeout(() => {
1522
1527
if (!content) content = message_storage[message_id];
1523
1528
html = markdown_render(content);
@@ -1538,7 +1543,7 @@ function update_message(content_map, message_id, content = null) {
1538
1543
content_map.inner.innerHTML = html;
1539
1544
content_map.count.innerText = count_words_and_tokens(message_storage[message_id], provider_storage[message_id]?.model);
1540
1545
highlight(content_map.inner);
1541
if (content_map.message_index == -1) {
1546
if (scroll) {
1542
1547
lazy_scroll_to_bottom();
1543
1548
}
1544
1549
content_map.update_timeouts.forEach((timeoutId)=>clearTimeout(timeoutId));
@@ -1890,7 +1895,7 @@ fileInput.addEventListener('change', async (event) => {
1890
1895
fileInput.value = "";
1891
1896
inputCount.innerText = `${count} Conversations were imported successfully`;
1892
1897
} else {
1893
is_cookie_file = false;
1898
is_cookie_file = data.api_key;
1894
1899
if (Array.isArray(data)) {
1895
1900
data.forEach((item) => {
1896
1901
if (item.domain && item.name && item.value) {
@@ -1927,7 +1932,7 @@ function get_selected_model() {
1927
1932
}
1928
1933
}
1929
1934
1930
async function api(ressource, args=null, files=null, message_id=null) {
1935
async function api(ressource, args=null, files=null, message_id=null, scroll=true) {
1931
1936
let api_key;
1932
1937
if (ressource == "models" && args) {
1933
1938
api_key = get_api_key_by_provider(args);
@@ -1957,7 +1962,7 @@ async function api(ressource, args=null, files=null, message_id=null) {
1957
1962
headers: headers,
1958
1963
body: body,
1959
1964
});
1960
return read_response(response, message_id, args.provider || null);
1965
return read_response(response, message_id, args.provider || null, scroll);
1961
1966
}
1962
1967
response = await fetch(url, {headers: headers});
1963
1968
if (response.status == 200) {
@@ -1966,7 +1971,7 @@ async function api(ressource, args=null, files=null, message_id=null) {
1966
1971
console.error(response);
1967
1972
}
1968
1973
1969
async function read_response(response, message_id, provider) {
1974
async function read_response(response, message_id, provider, scroll) {
1970
1975
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
1971
1976
let buffer = ""
1972
1977
while (true) {
@@ -1979,7 +1984,7 @@ async function read_response(response, message_id, provider) {
1979
1984
continue;
1980
1985
}
1981
1986
try {
1982
add_message_chunk(JSON.parse(buffer + line), message_id, provider);
1987
add_message_chunk(JSON.parse(buffer + line), message_id, provider, scroll);
1983
1988
buffer = "";
1984
1989
} catch {
1985
1990
buffer += line
@@ -2106,6 +2111,7 @@ if (SpeechRecognition) {
2106
2111
recognition.maxAlternatives = 1;
2107
2112
2108
2113
let startValue;
2114
let buffer;
2109
2115
let lastDebounceTranscript;
2110
2116
recognition.onstart = function() {
2111
2117
microLabel.classList.add("recognition");
@@ -2114,6 +2120,7 @@ if (SpeechRecognition) {
2114
2120
messageInput.readOnly = true;
2115
2121
};
2116
2122
recognition.onend = function() {
2123
messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2117
2124
messageInput.readOnly = false;
2118
2125
messageInput.focus();
2119
2126
};
@@ -2131,18 +2138,17 @@ if (SpeechRecognition) {
2131
2138
lastDebounceTranscript = transcript;
2132
2139
}
2133
2140
if (transcript) {
2134
messageInput.value = `${startValue ? startValue+"\n" : ""}${transcript.trim()}`;
2141
inputCount.innerText = transcript;
2135
2142
if (isFinal) {
2136
startValue = messageInput.value;
2143
buffer = `${buffer ? buffer + "\n" : ""}${transcript.trim()}`;
2137
2144
}
2138
messageInput.style.height = messageInput.scrollHeight + "px";
2139
messageInput.scrollTop = messageInput.scrollHeight;
2140
2145
}
2141
2146
};
2142
2147
2143
2148
microLabel.addEventListener("click", (e) => {
2144
2149
if (microLabel.classList.contains("recognition")) {
2145
2150
recognition.stop();
2151
messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2146
2152
microLabel.classList.remove("recognition");
2147
2153
} else {
2148
2154
const lang = document.getElementById("recognition-language")?.value;
@@ -9,6 +9,8 @@ import shutil
9
9
from flask import Flask, Response, request, jsonify
10
10
from typing import Generator
11
11
from pathlib import Path
12
from urllib.parse import quote_plus
13
from hashlib import sha256
12
14
from werkzeug.utils import secure_filename
13
15
14
16
from ...image import is_allowed_extension, to_image
@@ -123,15 +125,30 @@ class Backend_Api(Api):
123
125
"type": "function"
124
126
})
125
127
do_filter_markdown = request.args.get("filter_markdown")
126
response = iter_run_tools(
127
ChatCompletion.create,
128
model=request.args.get("model"),
129
messages=[{"role": "user", "content": request.args.get("prompt")}],
130
provider=request.args.get("provider", None),
131
stream=not do_filter_markdown,
132
ignore_stream=not request.args.get("stream"),
133
tool_calls=tool_calls,
134
)
128
cache_id = request.args.get('cache')
129
parameters = {
130
"model": request.args.get("model"),
131
"messages": [{"role": "user", "content": request.args.get("prompt")}],
132
"provider": request.args.get("provider", None),
133
"stream": not do_filter_markdown and not cache_id,
134
"ignore_stream": not request.args.get("stream"),
135
"tool_calls": tool_calls,
136
}
137
if cache_id:
138
cache_id = sha256(cache_id.encode() + json.dumps(parameters, sort_keys=True).encode()).hexdigest()
139
cache_dir = Path(get_cookies_dir()) / ".scrape_cache" / "create"
140
cache_file = cache_dir / f"{quote_plus(request.args.get('prompt').strip()[:20])}.{cache_id}.txt"
141
if cache_file.exists():
142
with cache_file.open("r") as f:
143
response = f.read()
144
else:
145
response = iter_run_tools(ChatCompletion.create, **parameters)
146
cache_dir.mkdir(parents=True, exist_ok=True)
147
with cache_file.open("w") as f:
148
f.write(response)
149
else:
150
response = iter_run_tools(ChatCompletion.create, **parameters)
151
135
152
if do_filter_markdown:
136
153
return Response(filter_markdown(response, do_filter_markdown), mimetype='text/plain')
137
154
def cast_str():
@@ -269,7 +269,7 @@ class AsyncProvider(AbstractProvider):
269
269
def get_async_create_function(cls) -> callable:
270
270
return cls.create_async
271
271
272
class AsyncGeneratorProvider(AsyncProvider):
272
class AsyncGeneratorProvider(AbstractProvider):
273
273
"""
274
274
Provides asynchronous generator functionality for streaming results.
275
275
"""
@@ -395,6 +395,10 @@ class AsyncAuthedProvider(AsyncGeneratorProvider):
395
395
def get_async_create_function(cls) -> callable:
396
396
return cls.create_async_generator
397
397
398
@classmethod
399
def get_cache_file(cls) -> Path:
400
return Path(get_cookies_dir()) / f"auth_{cls.parent if hasattr(cls, 'parent') else cls.__name__}.json"
401
398
402
@classmethod
399
403
def create_completion(
400
404
cls,
@@ -404,18 +408,24 @@ class AsyncAuthedProvider(AsyncGeneratorProvider):
404
408
) -> CreateResult:
405
409
try:
406
410
auth_result = AuthResult()
407
cache_file = Path(get_cookies_dir()) / f"auth_{cls.parent if hasattr(cls, 'parent') else cls.__name__}.json"
411
cache_file = cls.get_cache_file()
408
412
if cache_file.exists():
409
413
with cache_file.open("r") as f:
410
414
auth_result = AuthResult(**json.load(f))
411
415
else:
412
416
auth_result = cls.on_auth(**kwargs)
413
return to_sync_generator(cls.create_authed(model, messages, auth_result, **kwargs))
417
if hasattr(auth_result, "_iter__"):
418
for chunk in auth_result:
419
if isinstance(chunk, AsyncResult):
420
auth_result = chunk
421
else:
422
yield chunk
423
yield from to_sync_generator(cls.create_authed(model, messages, auth_result, **kwargs))
414
424
except (MissingAuthError, NoValidHarFileError):
415
425
if cache_file.exists():
416
426
cache_file.unlink()
417
427
auth_result = cls.on_auth(**kwargs)
418
return to_sync_generator(cls.create_authed(model, messages, auth_result, **kwargs))
428
yield from to_sync_generator(cls.create_authed(model, messages, auth_result, **kwargs))
419
429
finally:
420
430
cache_file.parent.mkdir(parents=True, exist_ok=True)
421
431
cache_file.write_text(json.dumps(auth_result.get_dict()))
@@ -434,6 +444,12 @@ class AsyncAuthedProvider(AsyncGeneratorProvider):
434
444
auth_result = AuthResult(**json.load(f))
435
445
else:
436
446
auth_result = await cls.on_auth_async(**kwargs)
447
if hasattr(auth_result, "_aiter__"):
448
async for chunk in auth_result:
449
if isinstance(chunk, AsyncResult):
450
auth_result = chunk
451
else:
452
yield chunk
437
453
response = to_async_iterator(cls.create_authed(model, messages, **kwargs, auth_result=auth_result))
438
454
async for chunk in response:
439
455
yield chunk
@@ -19,7 +19,9 @@ def quote_url(url: str) -> str:
19
19
20
20
def quote_title(title: str) -> str:
21
21
if title:
22
return title.replace("\n", "").replace('"', '')
22
title = title.strip()
23
title = " ".join(title.split())
24
return title.replace('[', '').replace(']', '')
23
25
return ""
24
26
25
27
def format_link(url: str, title: str = None) -> str: