返回提交历史
Modified
g4f/Provider/PollinationsAI.py
+25
-9
Modified
g4f/Provider/PollinationsImage.py
+3
-1
Modified
g4f/gui/client/background.html
+39
-15
Modified
g4f/gui/client/index.html
+1
-1
Modified
g4f/gui/client/qrcode.html
+5
-5
Modified
g4f/gui/client/static/js/chat.v1.js
+28
-31
Modified
g4f/gui/client/static/js/photoswipe.js
+1
-1
Modified
g4f/gui/server/backend_api.py
+15
-13
Modified
g4f/gui/server/website.py
+6
-6
Modified
g4f/image/copy_images.py
+3
-1
Modified
g4f/requests/__init__.py
+1
-0
XFEstudio/gpt4free
Improve background page Improve share js functions Fix photoswipe in UI Support n parameter in PollinationsAI
a1871daf
代码差异
11 个文件
+127
-83
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
import json
4
4
import random
5
5
import requests
6
import asyncio
6
7
from urllib.parse import quote_plus
7
8
from typing import Optional
8
9
from aiohttp import ClientSession
@@ -148,6 +149,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
148
149
private: bool = False,
149
150
enhance: bool = False,
150
151
safe: bool = False,
152
n: int = 1,
151
153
# Text generation parameters
152
154
media: MediaListType = None,
153
155
temperature: float = None,
@@ -187,7 +189,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
187
189
nologo=nologo,
188
190
private=private,
189
191
enhance=enhance,
190
safe=safe
192
safe=safe,
193
n=n
191
194
):
192
195
yield chunk
193
196
else:
@@ -223,12 +226,10 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
223
226
nologo: bool,
224
227
private: bool,
225
228
enhance: bool,
226
safe: bool
229
safe: bool,
230
n: int
227
231
) -> AsyncResult:
228
if not cache and seed is None:
229
seed = random.randint(9999, 99999999)
230
232
params = use_aspect_ratio({
231
"seed": seed,
232
233
"width": width,
233
234
"height": height,
234
235
"model": model,
@@ -238,12 +239,27 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
238
239
"safe": str(safe).lower()
239
240
}, aspect_ratio)
240
241
query = "&".join(f"{k}={quote_plus(str(v))}" for k, v in params.items() if v is not None)
241
prompt = quote_plus(prompt)[:8112] # Limit URL length
242
prompt = quote_plus(prompt)[:2048-256-len(query)]
242
243
url = f"{cls.image_api_endpoint}prompt/{prompt}?{query}"
244
def get_image_url(i: int = 0, seed: Optional[int] = None):
245
if i == 0:
246
if not cache and seed is None:
247
seed = random.randint(0, 2**32)
248
else:
249
seed = random.randint(0, 2**32)
250
return f"{url}&seed={seed}" if seed else url
243
251
async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session:
244
async with session.get(url, allow_redirects=False) as response:
245
await raise_for_status(response)
246
yield ImageResponse(str(response.url), prompt)
252
async def get_image(i: int = 0, seed: Optional[int] = None):
253
async with session.get(get_image_url(i, seed), allow_redirects=False) as response:
254
try:
255
await raise_for_status(response)
256
except Exception as e:
257
debug.error(f"Error fetching image: {e}")
258
return str(response.url)
259
return str(response.url)
260
yield ImageResponse(await asyncio.gather(*[
261
get_image(i, seed) for i in range(int(n))
262
]), prompt)
247
263
248
264
@classmethod
249
265
async def _generate_text(
@@ -45,6 +45,7 @@ class PollinationsImage(PollinationsAI):
45
45
private: bool = False,
46
46
enhance: bool = False,
47
47
safe: bool = False,
48
n: int = 4,
48
49
**kwargs
49
50
) -> AsyncResult:
50
51
# Calling model updates before creating a generator
@@ -61,6 +62,7 @@ class PollinationsImage(PollinationsAI):
61
62
nologo=nologo,
62
63
private=private,
63
64
enhance=enhance,
64
safe=safe
65
safe=safe,
66
n=n
65
67
):
66
68
yield chunk
@@ -28,7 +28,6 @@
28
28
29
29
.gradient {
30
30
position: absolute;
31
z-index: -1;
32
31
left: 50vw;
33
32
border-radius: 50%;
34
33
background: radial-gradient(circle at center, var(--accent), var(--gradient));
@@ -73,12 +72,6 @@
73
72
overflow: hidden;
74
73
}
75
74
76
iframe {
77
background: transparent;
78
width: 100%;
79
border: none;
80
}
81
82
75
.hidden {
83
76
display: none;
84
77
}
@@ -86,7 +79,6 @@
86
79
#background, #image-feed, #video-feed {
87
80
height: 100%;
88
81
position: absolute;
89
z-index: -1;
90
82
object-fit: cover;
91
83
object-position: center;
92
84
width: 100%;
@@ -100,29 +92,41 @@
100
92
<video id="video-feed" class="hidden" alt="Video Feed" src="/search/video" autoplay></video>
101
93
102
94
<!-- Gradient Background Circle -->
103
<div class="gradient"></div></div>
95
<div class="gradient"></div>
104
96
<script>
105
97
(async () => {
106
98
const url = "https://image.pollinations.ai/feed";
107
99
const imageFeed = document.getElementById("image-feed");
108
100
const videoFeed = document.getElementById("video-feed");
109
101
const gradient = document.querySelector(".gradient");
110
const images = []
102
const images = [];
111
103
let es = null;
112
104
let skipVideo = 1;
113
let errorVideo = false;
105
let skipImage = 0;
106
let errorVideo = 0;
107
let errorImage = 0;
114
108
videoFeed.onloadeddata = () => {
115
109
videoFeed.classList.remove("hidden");
116
110
gradient.classList.add("hidden");
117
111
};
118
videoFeed.onerror = () => {
112
videoFeed.onerror = (e) => {
119
113
videoFeed.classList.add("hidden");
120
errorVideo = true;
114
errorVideo += 1;
115
if (errorVideo > 3) {
116
gradient.classList.remove("hidden");
117
return;
118
}
119
videoFeed.src = "/search/video?skip=" + skipVideo;
120
skipVideo++;
121
121
};
122
122
videoFeed.onended = () => {
123
123
videoFeed.src = "/search/video?skip=" + skipVideo;
124
124
skipVideo++;
125
125
};
126
videoFeed.onclick = () => {
127
videoFeed.src = "/search/video?skip=" + skipVideo;
128
skipVideo++;
129
};
126
130
function initES() {
127
131
if (es == null || es.readyState == EventSource.CLOSED) {
128
132
const eventSource = new EventSource(url);
@@ -152,9 +156,21 @@
152
156
}
153
157
}
154
158
}
155
initES();
159
let refreshOnHide = true;
160
document.addEventListener("visibilitychange", () => {
161
if (document.hidden) {
162
refreshOnHide = false;
163
} else {
164
refreshOnHide = true;
165
}
166
});
156
167
setInterval(() => {
157
if (!errorVideo) {
168
if (errorVideo < 3 || !refreshOnHide) {
169
return;
170
}
171
if (errorImage < 3) {
172
imageFeed.src = "/search/image+g4f?skip=" + skipImage;
173
skipImage++;
158
174
return;
159
175
}
160
176
if (images.length > 0) {
@@ -168,6 +184,14 @@
168
184
imageFeed.onerror = () => {
169
185
imageFeed.classList.add("hidden");
170
186
gradient.classList.remove("hidden");
187
errorImage++;
188
};
189
imageFeed.onload = () => {
190
imageFeed.classList.remove("hidden");
191
gradient.classList.add("hidden");
192
};
193
imageFeed.onclick = () => {
194
imageFeed.src = "/search/image?random=" + Math.random();
171
195
};
172
196
})();
173
197
</script>
@@ -63,7 +63,7 @@
63
63
<script src="/static/js/highlight.min.js" async></script>
64
64
<script>
65
65
window.conversation_id = "{{conversation_id}}";
66
window.chat_id = "{{chat_id}}";
66
window.share_id = "{{share_id}}";
67
67
window.share_url = "{{share_url}}";
68
68
window.start_id = "{{conversation_id}}";
69
69
</script>
@@ -74,15 +74,15 @@
74
74
});
75
75
76
76
document.getElementById('generateQRCode').addEventListener('click', async () => {
77
const chat_id = generate_uuid();
77
const share_id = generate_uuid();
78
78
79
const url = `${share_url}/backend-api/v2/chat/${encodeURI(chat_id)}`;
79
const url = `${share_url}/backend-api/v2/chat/${encodeURI(share_id)}`;
80
80
const response = await fetch(url, {
81
81
method: 'POST',
82
82
headers: {'content-type': 'application/json'},
83
83
body: localStorage.getItem(`conversation:${conversation_id}`)
84
84
});
85
const share = `${share_url}/chat/${encodeURI(chat_id)}/${encodeURI(conversation_id)}`;
85
const share = `${share_url}/chat/${encodeURI(share_id)}/${encodeURI(conversation_id)}`;
86
86
const qrcodeStatus = document.getElementById('qrcode-status');
87
87
if (response.status !== 200) {
88
88
qrcodeStatus.innerText = 'Error generating QR code: ' + response.statusText;
@@ -115,8 +115,8 @@
115
115
116
116
const constraints = {
117
117
video: {
118
width: { ideal: 1280 },
119
height: { ideal: 1280 },
118
width: { ideal: 800 },
119
height: { ideal: 800 },
120
120
facingMode: facingMode
121
121
},
122
122
audio: false
@@ -298,8 +298,8 @@ const register_message_buttons = async () => {
298
298
if (message_el) {
299
299
if ("index" in message_el.dataset) {
300
300
await remove_message(window.conversation_id, message_el.dataset.index);
301
chatBody.removeChild(message_el);
301
302
}
302
message_el.remove();
303
303
}
304
304
reloadConversation = true;
305
305
await safe_load_conversation(window.conversation_id, false);
@@ -842,7 +842,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
842
842
for (const [key, value] of Object.entries(message.conversation)) {
843
843
conversation.data[key] = value;
844
844
}
845
await save_conversation(conversation_id, conversation);
845
await save_conversation(conversation_id, get_conversation_data(conversation));
846
846
} else if (message.type == "auth") {
847
847
error_storage[message_id] = message.message
848
848
content_map.inner.innerHTML += markdown_render(`**An error occured:** ${message.message}`);
@@ -1227,7 +1227,6 @@ function sanitize(input, replacement) {
1227
1227
}
1228
1228
1229
1229
async function set_conversation_title(conversation_id, title) {
1230
window.chat_id = null;
1231
1230
conversation = await get_conversation(conversation_id)
1232
1231
conversation.new_title = title;
1233
1232
const new_id = sanitize(title, " ");
@@ -1319,7 +1318,6 @@ const set_conversation = async (conversation_id) => {
1319
1318
1320
1319
const new_conversation = async () => {
1321
1320
history.pushState({}, null, `/chat/`);
1322
window.chat_id = null;
1323
1321
window.conversation_id = uuid();
1324
1322
document.title = window.title || document.title;
1325
1323
document.querySelector(".chat-header").innerText = "New Conversation - G4F";
@@ -1391,7 +1389,7 @@ const load_conversation = async (conversation, scroll=true) => {
1391
1389
document.title = title;
1392
1390
}
1393
1391
const chatHeader = document.querySelector(".chat-header");
1394
if (window.chat_id) {
1392
if (window.share_id && conversation.id == window.start_id) {
1395
1393
chatHeader.innerHTML = '<i class="fa-solid fa-qrcode"></i> ' + escapeHtml(title);
1396
1394
} else {
1397
1395
chatHeader.innerText = title;
@@ -1581,16 +1579,16 @@ async function get_conversation(conversation_id) {
1581
1579
return conversation;
1582
1580
}
1583
1581
1584
async function save_conversation(conversation_id, conversation) {
1582
function get_conversation_data(conversation) {
1585
1583
conversation.updated = Date.now();
1586
const data = JSON.stringify(conversation)
1584
return JSON.stringify(conversation);
1585
}
1586
1587
async function save_conversation(conversation_id, data) {
1587
1588
appStorage.setItem(
1588
1589
`conversation:${conversation_id}`,
1589
1590
data
1590
1591
);
1591
if (conversation_id != window.start_id) {
1592
window.chat_id = null;
1593
}
1594
1592
}
1595
1593
1596
1594
async function get_messages(conversation_id) {
@@ -1600,13 +1598,13 @@ async function get_messages(conversation_id) {
1600
1598
1601
1599
async function add_conversation(conversation_id) {
1602
1600
if (appStorage.getItem(`conversation:${conversation_id}`) == null) {
1603
await save_conversation(conversation_id, {
1601
await save_conversation(conversation_id, get_conversation_data({
1604
1602
id: conversation_id,
1605
1603
title: "",
1606
1604
added: Date.now(),
1607
1605
system: chatPrompt?.value,
1608
1606
items: [],
1609
});
1607
}));
1610
1608
}
1611
1609
try {
1612
1610
add_url_to_history(`/chat/${conversation_id}`);
@@ -1622,7 +1620,7 @@ async function save_system_message() {
1622
1620
const conversation = await get_conversation(window.conversation_id);
1623
1621
if (conversation) {
1624
1622
conversation.system = chatPrompt?.value;
1625
await save_conversation(window.conversation_id, conversation);
1623
await save_conversation(window.conversation_id, get_conversation_data(conversation));
1626
1624
}
1627
1625
}
1628
1626
@@ -1640,9 +1638,10 @@ const remove_message = async (conversation_id, index) => {
1640
1638
}
1641
1639
}
1642
1640
conversation.items = new_items;
1643
await save_conversation(conversation_id, conversation);
1644
if (window.chat_id && window.conversation_id == window.start_id) {
1645
const url = `${window.share_url}/backend-api/v2/chat/${window.chat_id}`;
1641
const data = get_conversation_data(conversation);
1642
await save_conversation(conversation_id, data);
1643
if (window.share_id && window.conversation_id == window.start_id) {
1644
const url = `${window.share_url}/backend-api/v2/chat/${window.share_id}`;
1646
1645
await fetch(url, {
1647
1646
method: 'POST',
1648
1647
headers: {'content-type': 'application/json'},
@@ -1716,13 +1715,14 @@ const add_message = async (
1716
1715
});
1717
1716
conversation.items = new_messages;
1718
1717
}
1719
await save_conversation(conversation_id, conversation);
1720
if (window.chat_id && conversation_id == window.start_id) {
1721
const url = `${window.share_url}/backend-api/v2/chat/${window.chat_id}`;
1718
data = get_conversation_data(conversation);
1719
await save_conversation(conversation_id, data);
1720
if (window.share_id && conversation_id == window.start_id) {
1721
const url = `${window.share_url}/backend-api/v2/chat/${window.share_id}`;
1722
1722
fetch(url, {
1723
1723
method: 'POST',
1724
1724
headers: {'content-type': 'application/json'},
1725
body: JSON.stringify(conversation),
1725
body: data
1726
1726
});
1727
1727
}
1728
1728
return conversation.items.length - 1;
@@ -1758,7 +1758,7 @@ const load_conversations = async () => {
1758
1758
// appStorage.removeItem(`conversation:${conversation.id}`);
1759
1759
// return;
1760
1760
// }
1761
const shareIcon = (conversation.id == window.start_id && window.chat_id) ? '<i class="fa-solid fa-qrcode"></i>': '';
1761
const shareIcon = (conversation.id == window.start_id && window.share_id) ? '<i class="fa-solid fa-qrcode"></i>': '';
1762
1762
html.push(`
1763
1763
<div class="convo" id="convo-${conversation.id}">
1764
1764
<div class="left" onclick="set_conversation('${conversation.id}')">
@@ -2071,14 +2071,14 @@ chatPrompt.addEventListener("input", function() {
2071
2071
});
2072
2072
2073
2073
window.addEventListener('load', async function() {
2074
if (!window.chat_id) {
2074
if (!window.share_id) {
2075
2075
return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`)));
2076
2076
}
2077
2077
if (!window.conversation_id) {
2078
window.conversation_id = window.chat_id;
2078
window.conversation_id = window.share_id;
2079
2079
}
2080
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id ? window.chat_id : window.conversation_id}`, {
2081
headers: {'accept': 'application/json'},
2080
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.share_id}`, {
2081
headers: {'accept': 'application/json', 'x-conversation-id': window.conversation_id},
2082
2082
});
2083
2083
if (!response.ok) {
2084
2084
return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`)));
@@ -2087,10 +2087,7 @@ window.addEventListener('load', async function() {
2087
2087
if (!window.conversation_id || conversation.id == window.conversation_id) {
2088
2088
window.conversation_id = conversation.id;
2089
2089
await load_conversation(conversation);
2090
appStorage.setItem(
2091
`conversation:${conversation.id}`,
2092
JSON.stringify(conversation)
2093
);
2090
await save_conversation(window.conversation_id, JSON.stringify(conversation));
2094
2091
await load_conversations();
2095
2092
let refreshOnHide = true;
2096
2093
document.addEventListener("visibilitychange", () => {
@@ -2101,7 +2098,7 @@ window.addEventListener('load', async function() {
2101
2098
}
2102
2099
});
2103
2100
var refreshIntervalId = setInterval(async () => {
2104
if (!window.chat_id) {
2101
if (!window.share_id) {
2105
2102
clearInterval(refreshIntervalId);
2106
2103
return;
2107
2104
}
@@ -2111,7 +2108,7 @@ window.addEventListener('load', async function() {
2111
2108
if (window.conversation_id != window.start_id) {
2112
2109
return;
2113
2110
}
2114
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id}`, {
2111
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.share_id}`, {
2115
2112
headers: {
2116
2113
'accept': 'application/json',
2117
2114
'if-none-match': conversation.updated,
@@ -4,7 +4,7 @@ import PhotoSwipeAutoHideUI from "https://cdn.jsdelivr.net/gh/arnowelzel/photosw
4
4
import PhotoSwipeSlideshow from "https://cdn.jsdelivr.net/gh/dpet23/photoswipe-slideshow@v2.0.0/photoswipe-slideshow.esm.min.js";
5
5
6
6
const lightbox = new PhotoSwipeLightbox({
7
gallery: '#messages',
7
gallery: '#chatBody',
8
8
children: 'a:has(img)',
9
9
initialZoomLevel: 'fill',
10
10
secondaryZoomLevel: 1,
@@ -371,6 +371,8 @@ class Backend_Api(Api):
371
371
match_files = [file for file, count in match_files.items() if count >= request.args.get("min", len(search))]
372
372
if int(request.args.get("skip", 0)) >= len(match_files):
373
373
return jsonify({"error": {"message": "Not found"}}), 404
374
if (request.args.get("random", False)):
375
return redirect(f"/media/{random.choice(match_files)}"), 302
374
376
return redirect(f"/media/{match_files[int(request.args.get("skip", 0))]}"), 302
375
377
376
378
@app.route('/backend-api/v2/upload_cookies', methods=['POST'])
@@ -386,12 +388,12 @@ class Backend_Api(Api):
386
388
return "File saved", 200
387
389
return 'Not supported file', 400
388
390
389
@self.app.route('/backend-api/v2/chat/<chat_id>', methods=['GET'])
390
def get_chat(chat_id: str) -> str:
391
chat_id = secure_filename(chat_id)
392
if self.chat_cache.get(chat_id, 0) == request.headers.get("if-none-match", 0):
391
@self.app.route('/backend-api/v2/chat/<share_id>', methods=['GET'])
392
def get_chat(share_id: str) -> str:
393
share_id = secure_filename(share_id)
394
if self.chat_cache.get(share_id, 0) == request.headers.get("if-none-match", 0):
393
395
return jsonify({"error": {"message": "Not modified"}}), 304
394
bucket_dir = get_bucket_dir(chat_id)
396
bucket_dir = get_bucket_dir(share_id)
395
397
file = os.path.join(bucket_dir, "chat.json")
396
398
if not os.path.isfile(file):
397
399
return jsonify({"error": {"message": "Not found"}}), 404
@@ -399,23 +401,23 @@ class Backend_Api(Api):
399
401
chat_data = json.load(f)
400
402
if chat_data.get("updated", 0) == request.headers.get("if-none-match", 0):
401
403
return jsonify({"error": {"message": "Not modified"}}), 304
402
self.chat_cache[chat_id] = chat_data.get("updated", 0)
404
self.chat_cache[share_id] = chat_data.get("updated", 0)
403
405
return jsonify(chat_data), 200
404
406
405
@self.app.route('/backend-api/v2/chat/<chat_id>', methods=['POST'])
406
def upload_chat(chat_id: str) -> dict:
407
@self.app.route('/backend-api/v2/chat/<share_id>', methods=['POST'])
408
def upload_chat(share_id: str) -> dict:
407
409
chat_data = {**request.json}
408
410
updated = chat_data.get("updated", 0)
409
cache_value = self.chat_cache.get(chat_id, 0)
411
cache_value = self.chat_cache.get(share_id, 0)
410
412
if updated == cache_value:
411
413
return jsonify({"error": {"message": "invalid date"}}), 400
412
chat_id = secure_filename(chat_id)
413
bucket_dir = get_bucket_dir(chat_id)
414
share_id = secure_filename(share_id)
415
bucket_dir = get_bucket_dir(share_id)
414
416
os.makedirs(bucket_dir, exist_ok=True)
415
417
with open(os.path.join(bucket_dir, "chat.json"), 'w') as f:
416
418
json.dump(chat_data, f)
417
self.chat_cache[chat_id] = updated
418
return {"chat_id": chat_id}
419
self.chat_cache[share_id] = updated
420
return {"share_id": share_id}
419
421
420
422
def handle_synthesize(self, provider: str):
421
423
try:
@@ -19,12 +19,12 @@ class Website:
19
19
'function': self._chat,
20
20
'methods': ['GET', 'POST']
21
21
},
22
'/chat/<chat_id>/': {
23
'function': self._chat_id,
22
'/chat/<share_id>/': {
23
'function': self._share_id,
24
24
'methods': ['GET', 'POST']
25
25
},
26
'/chat/<chat_id>/<conversation_id>': {
27
'function': self._chat_id,
26
'/chat/<share_id>/<conversation_id>': {
27
'function': self._share_id,
28
28
'methods': ['GET', 'POST']
29
29
},
30
30
'/chat/menu/': {
@@ -50,9 +50,9 @@ class Website:
50
50
return render_template('index.html', conversation_id=str(uuid.uuid4()))
51
51
return render_template('index.html', conversation_id=conversation_id)
52
52
53
def _chat_id(self, chat_id, conversation_id: str = ""):
53
def _share_id(self, share_id, conversation_id: str = ""):
54
54
share_url = os.environ.get("G4F_SHARE_URL", "")
55
return render_template('index.html', share_url=share_url, chat_id=chat_id, conversation_id=conversation_id)
55
return render_template('index.html', share_url=share_url, share_id=share_id, conversation_id=conversation_id)
56
56
57
57
def _index(self):
58
58
return render_template('index.html', conversation_id=str(uuid.uuid4()))
@@ -25,8 +25,10 @@ def get_media_extension(media: str) -> str:
25
25
"""Extract media file extension from URL or filename"""
26
26
match = re.search(r"\.(j?[a-z]{3})(?:\?|$)", media, re.IGNORECASE)
27
27
extension = match.group(1).lower() if match else ""
28
if not extension:
29
return ""
28
30
if extension not in EXTENSIONS_MAP:
29
raise ValueError(f"Unsupported media extension: {extension}")
31
raise ValueError(f"Unsupported media extension: {extension} in: {media}")
30
32
return f".{extension}"
31
33
32
34
def ensure_images_dir():
@@ -154,6 +154,7 @@ async def get_nodriver(
154
154
if not os.path.exists(browser_executable_path):
155
155
browser_executable_path = None
156
156
lock_file = Path(get_cookies_dir()) / ".nodriver_is_open"
157
lock_file.parent.mkdir(exist_ok=True)
157
158
# Implement a short delay (milliseconds) to prevent race conditions.
158
159
await asyncio.sleep(0.1 * random.randint(0, 50))
159
160
if lock_file.exists():