返回提交历史
Modified
g4f/Provider/ARTA.py
+8
-5
Modified
g4f/Provider/PollinationsAI.py
+3
-3
Modified
g4f/gui/client/index.html
+2
-1
Modified
g4f/gui/client/static/css/style.css
+7
-1
Modified
g4f/gui/client/static/js/chat.v1.js
+32
-19
Modified
g4f/gui/server/api.py
+2
-2
Modified
g4f/gui/server/website.py
+5
-1
Modified
g4f/image/copy_images.py
+1
-1
Modified
g4f/providers/response.py
+6
-0
XFEstudio/gpt4free
Use loading icon
13810b8a
代码差异
9 个文件
+66
-33
@@ -175,8 +175,9 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
175
175
176
176
# Step 3: Check Generation Status
177
177
status_url = cls.status_check_url.format(record_id=record_id)
178
counter = 0
178
counter = 4
179
179
start_time = time.time()
180
last_status = None
180
181
while True:
181
182
async with session.get(status_url, headers=headers, proxy=proxy) as status_response:
182
183
status_data = await status_response.json()
@@ -189,10 +190,12 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
189
190
yield ImageResponse(images=image_urls, alt=prompt)
190
191
return
191
192
elif status in ("IN_QUEUE", "IN_PROGRESS"):
192
yield Reasoning(label=("Waiting" if status == "IN_QUEUE" else "Generating"), status="." * counter)
193
if last_status != status:
194
last_status = status
195
if status == "IN_QUEUE":
196
yield Reasoning(label="Waiting", ticker="⌛" * counter)
197
else:
198
yield Reasoning(label="Generating", ticker="⚽" * counter)
193
199
await asyncio.sleep(2) # Poll every 2 seconds
194
counter += 1
195
if counter > 3:
196
counter = 1
197
200
else:
198
201
raise ResponseError(f"Image generation failed with status: {status}")
@@ -238,12 +238,12 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
238
238
"safe": str(safe).lower()
239
239
}, aspect_ratio)
240
240
query = "&".join(f"{k}={quote_plus(str(v))}" for k, v in params.items() if v is not None)
241
url = f"{cls.image_api_endpoint}prompt/{quote_plus(prompt)}?{query}"
242
url = url[:8192] # Limit URL length
241
prompt = quote_plus(prompt)[:8112] # Limit URL length
242
url = f"{cls.image_api_endpoint}prompt/{prompt}?{query}"
243
243
async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session:
244
244
async with session.get(url, allow_redirects=False) as response:
245
245
await raise_for_status(response)
246
yield ImageResponse(response.headers.get("location", str(response.url)), prompt)
246
yield ImageResponse(str(response.url), prompt)
247
247
248
248
@classmethod
249
249
async def _generate_text(
@@ -61,8 +61,9 @@
61
61
const gpt_image = '<img src="/static/img/gpt.png" alt="your avatar">';
62
62
</script>
63
63
<script src="/static/js/highlight.min.js" async></script>
64
64
65
<script>window.conversation_id = "{{conversation_id}}"</script>
65
<script>window.chat_id = "{{chat_id}}"</script>
66
<script>window.chat_id = "{{chat_id}}"; window.share_url = "{{share_url}}";</script>
66
67
<title>G4F Chat</title>
67
68
</head>
68
69
<body>
@@ -119,6 +119,7 @@ body:not(.white) a:visited{
119
119
color: var(--colour-3);
120
120
border: var(--colour-1) 1px solid;
121
121
border-radius: var(--border-radius-1);
122
z-index: 1;
122
123
}
123
124
124
125
.white .new_version {
@@ -351,6 +352,12 @@ body:not(.white) a:visited{
351
352
352
353
.message .reasoning_title {
353
354
cursor: pointer;
355
height: 22px;
356
overflow: hidden;
357
}
358
359
.message .reasoning_title strong {
360
float: left;
354
361
}
355
362
356
363
.message .user i {
@@ -1164,7 +1171,6 @@ ul {
1164
1171
.sidebar.shown {
1165
1172
width: 400px;
1166
1173
padding: 15px;
1167
margin-right: 10px;
1168
1174
}
1169
1175
1170
1176
/* style for hljs copy */
@@ -94,7 +94,8 @@ function render_reasoning(reasoning, final = false) {
94
94
</div>` : "";
95
95
return `<div class="reasoning_body">
96
96
<div class="reasoning_title">
97
<strong>${reasoning.label ? reasoning.label :'Reasoning <i class="brain">🧠</i>'}:</strong> ${escapeHtml(reasoning.status)}
97
<strong>${reasoning.label ? reasoning.label :'Reasoning <i class="brain">🧠</i>'}: </strong>
98
${reasoning.status ? escapeHtml(reasoning.status) : ' <i class="fas fa-spinner fa-spin"></i>'}
98
99
</div>
99
100
${inner_text}
100
101
</div>`;
@@ -891,9 +892,11 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
891
892
} else if (message.type == "login") {
892
893
update_message(content_map, message_id, markdown_render(message.login), scroll);
893
894
} else if (message.type == "finish") {
894
finish_storage[message_id] = message.finish;
895
if (finish_message) {
896
await finish_message();
895
if (!finish_storage[message_id]) {
896
finish_storage[message_id] = message.finish;
897
if (finish_message) {
898
await finish_message();
899
}
897
900
}
898
901
} else if (message.type == "usage") {
899
902
usage_storage[message_id] = message.usage;
@@ -909,6 +912,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
909
912
} if (message.token) {
910
913
reasoning_storage[message_id].text += message.token;
911
914
}
915
reasoning_storage[message_id].ticker = message.ticker;
912
916
update_message(content_map, message_id, render_reasoning(reasoning_storage[message_id]), scroll);
913
917
} else if (message.type == "parameters") {
914
918
if (!parameters_storage[provider]) {
@@ -1012,7 +1016,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1012
1016
content_map.inner.innerHTML = html;
1013
1017
highlight(content_map.inner);
1014
1018
}
1015
if (message_storage[message_id] || reasoning_storage[message_id]) {
1019
if (message_storage[message_id] || reasoning_storage[message_id]?.status) {
1016
1020
const message_provider = message_id in provider_storage ? provider_storage[message_id] : null;
1017
1021
let usage = {};
1018
1022
if (usage_storage[message_id]) {
@@ -1164,11 +1168,6 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1164
1168
}, Object.values(image_storage), message_id, scroll, finish_message);
1165
1169
} catch (e) {
1166
1170
console.error(e);
1167
if (e.name != "AbortError") {
1168
error_storage[message_id] = true;
1169
content_map.inner.innerHTML += markdown_render(`**An error occured:** ${e}`);
1170
}
1171
await finish_message();
1172
1171
}
1173
1172
};
1174
1173
@@ -1296,7 +1295,6 @@ const delete_conversation = async (conversation_id) => {
1296
1295
};
1297
1296
1298
1297
const set_conversation = async (conversation_id) => {
1299
window.chat_id = null;
1300
1298
if (title_ids_storage[conversation_id]) {
1301
1299
conversation_id = title_ids_storage[conversation_id];
1302
1300
}
@@ -1315,15 +1313,17 @@ const set_conversation = async (conversation_id) => {
1315
1313
1316
1314
const new_conversation = async () => {
1317
1315
history.pushState({}, null, `/chat/`);
1316
window.chat_id = null;
1318
1317
window.conversation_id = uuid();
1319
1318
document.title = window.title || document.title;
1319
document.querySelector(".chat-header").innerText = "New Conversation - G4F";
1320
1320
1321
1321
await clear_conversation();
1322
1322
if (chatPrompt) {
1323
1323
chatPrompt.value = document.getElementById("systemPrompt")?.value;
1324
1324
}
1325
1325
load_conversations();
1326
hide_sidebar();
1326
hide_sidebar(true);
1327
1327
say_hello();
1328
1328
};
1329
1329
@@ -1384,7 +1384,12 @@ const load_conversation = async (conversation, scroll=true) => {
1384
1384
if (title) {
1385
1385
document.title = title;
1386
1386
}
1387
document.querySelector(".chat-header").innerText = title;
1387
const chatHeader = document.querySelector(".chat-header");
1388
if (window.chat_id) {
1389
chatHeader.innerHTML = '<i class="fa-solid fa-qrcode"></i> ' + escapeHtml(title);
1390
} else {
1391
chatHeader.innerText = title;
1392
}
1388
1393
1389
1394
if (chatPrompt) {
1390
1395
chatPrompt.value = conversation.system || "";
@@ -1577,6 +1582,9 @@ async function save_conversation(conversation_id, conversation) {
1577
1582
`conversation:${conversation_id}`,
1578
1583
data
1579
1584
);
1585
if (conversation_id != window.start_id) {
1586
window.chat_id = null;
1587
}
1580
1588
}
1581
1589
1582
1590
async function get_messages(conversation_id) {
@@ -1628,7 +1636,7 @@ const remove_message = async (conversation_id, index) => {
1628
1636
conversation.items = new_items;
1629
1637
await save_conversation(conversation_id, conversation);
1630
1638
if (window.chat_id) {
1631
const url = `/backend-api/v2/chat/${window.chat_id}`;
1639
const url = `${window.share_url}/backend-api/v2/chat/${window.chat_id}`;
1632
1640
await fetch(url, {
1633
1641
method: 'POST',
1634
1642
headers: {'content-type': 'application/json'},
@@ -1704,7 +1712,7 @@ const add_message = async (
1704
1712
}
1705
1713
await save_conversation(conversation_id, conversation);
1706
1714
if (window.chat_id) {
1707
const url = `/backend-api/v2/chat/${window.chat_id}`;
1715
const url = `${window.share_url}/backend-api/v2/chat/${window.chat_id}`;
1708
1716
fetch(url, {
1709
1717
method: 'POST',
1710
1718
headers: {'content-type': 'application/json'},
@@ -2053,7 +2061,8 @@ window.addEventListener('load', async function() {
2053
2061
if (!window.conversation_id) {
2054
2062
window.conversation_id = window.chat_id;
2055
2063
}
2056
const response = await fetch(`/backend-api/v2/chat/${window.chat_id ? window.chat_id : window.conversation_id}`, {
2064
window.start_id = window.conversation_id
2065
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id ? window.chat_id : window.conversation_id}`, {
2057
2066
headers: {'accept': 'application/json'},
2058
2067
});
2059
2068
if (!response.ok) {
@@ -2083,7 +2092,7 @@ window.addEventListener('load', async function() {
2083
2092
if (!refreshOnHide) {
2084
2093
return;
2085
2094
}
2086
const response = await fetch(`/backend-api/v2/chat/${window.chat_id}`, {
2095
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id}`, {
2087
2096
headers: {'accept': 'application/json', 'if-none-match': conversation.updated},
2088
2097
});
2089
2098
if (response.status == 200) {
@@ -2741,8 +2750,12 @@ async function api(ressource, args=null, files=null, message_id=null, scroll=tru
2741
2750
await finish_message();
2742
2751
return;
2743
2752
} else {
2744
await read_response(response, message_id, args.provider || null, scroll, finish_message);
2745
await finish_message();
2753
try {
2754
await read_response(response, message_id, args.provider || null, scroll, finish_message);
2755
await finish_message();
2756
} catch (e) {
2757
console.error(e);
2758
}
2746
2759
return;
2747
2760
}
2748
2761
} else if (args) {
@@ -214,12 +214,12 @@ class Api:
214
214
yield self._format_json(chunk.type, **chunk.get_dict())
215
215
else:
216
216
yield self._format_json("content", str(chunk))
217
yield from self._yield_logs()
218
217
except Exception as e:
219
218
logger.exception(e)
220
219
debug.error(e)
221
yield from self._yield_logs()
222
220
yield self._format_json('error', type(e).__name__, message=get_error_message(e))
221
finally:
222
yield from self._yield_logs()
223
223
224
224
def _yield_logs(self):
225
225
if debug.logs:
@@ -1,3 +1,6 @@
1
from __future__ import annotations
2
3
import os
1
4
import uuid
2
5
from flask import render_template, redirect
3
6
@@ -48,7 +51,8 @@ class Website:
48
51
return render_template('index.html', conversation_id=conversation_id)
49
52
50
53
def _chat_id(self, chat_id, conversation_id: str = ""):
51
return render_template('index.html', chat_id=chat_id, conversation_id=conversation_id)
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)
52
56
53
57
def _index(self):
54
58
return render_template('index.html', conversation_id=str(uuid.uuid4()))
@@ -135,7 +135,7 @@ async def copy_media(
135
135
async with session.get(image, ssl=request_ssl, headers=request_headers) as response:
136
136
response.raise_for_status()
137
137
media_type = response.headers.get("content-type", "application/octet-stream")
138
if media_type != "application/octet-stream":
138
if media_type not in ("application/octet-stream", "binary/octet-stream"):
139
139
if not is_valid_media_type(media_type):
140
140
raise ValueError(f"Unsupported media type: {media_type}")
141
141
with open(target_path, "wb") as f:
@@ -180,12 +180,14 @@ class Reasoning(ResponseType):
180
180
token: Optional[str] = None,
181
181
label: Optional[str] = None,
182
182
status: Optional[str] = None,
183
ticker: Optional[str] = None,
183
184
is_thinking: Optional[str] = None
184
185
) -> None:
185
186
"""Initialize with token, status, and thinking state."""
186
187
self.token = token
187
188
self.label = label
188
189
self.status = status
190
self.ticker = ticker
189
191
self.is_thinking = is_thinking
190
192
191
193
def __str__(self) -> str:
@@ -195,6 +197,8 @@ class Reasoning(ResponseType):
195
197
if self.token is not None:
196
198
return self.token
197
199
if self.status is not None:
200
if self.label is not None:
201
return f"{self.label}: {self.status}\n"
198
202
return f"{self.status}\n"
199
203
return ""
200
204
@@ -206,6 +210,8 @@ class Reasoning(ResponseType):
206
210
def get_dict(self) -> Dict:
207
211
"""Return a dictionary representation of the reasoning."""
208
212
if self.label is not None:
213
if self.ticker is not None:
214
return {"label": self.label, "status": self.status, "ticker": self.ticker}
209
215
return {"label": self.label, "status": self.status}
210
216
if self.is_thinking is None:
211
217
if self.status is None: